Plugin Directory

Changeset 3306340


Ignore:
Timestamp:
06/04/2025 11:22:36 AM (10 months ago)
Author:
zarhasan
Message:

Release v2.0.0 to trunk.

Location:
focusable/trunk
Files:
3 added
5 edited

Legend:

Unmodified
Added
Removed
  • focusable/trunk/assets/dist/css/style.css

    r2526498 r3306340  
    1 /*
    2 ===================
    3 Easing
    4 ===================
    5 */
    61.focus-source-key .focusable:focus {
    72  outline: none;
    83  box-shadow: none;
    94}
     5
    106.focus-source-key .focusable__ring {
    117  all: unset;
     
    2622  outline-offset: 2px;
    2723}
    28 .focus-source-key .focusable__ring[data-focusable-transition="1"], .focus-source-key .focusable__ring[data-focusable-transition=true] {
     24
     25.focus-source-key .focusable__ring[data-focusable-transition="1"],
     26.focus-source-key .focusable__ring[data-focusable-transition="true"] {
    2927  transition: all 400ms cubic-bezier(0.19, 1, 0.22, 1);
    3028}
  • focusable/trunk/assets/dist/js/main.js

    r2526498 r3306340  
    1 /******/ (() => { // webpackBootstrap
    2 /******/    var __webpack_modules__ = ({
     1function createFocusable() {
     2  if (!ally.hasOwnProperty("style") || !ally.hasOwnProperty("query")) {
     3    return;
     4  }
    35
    4 /***/ "./assets/src/js/main.js":
    5 /*!*******************************!*\
    6   !*** ./assets/src/js/main.js ***!
    7   \*******************************/
    8 /***/ (() => {
     6  const focusSource = ally.style.focusSource();
     7  const focusableElements = ally.query.focusable();
     8  let activeElement = ally.event.activeElement();
     9  let activeElementBoundingRect = {
     10    left: 0,
     11    top: 0,
     12    width: 0,
     13    height: 0,
     14  };
     15  const focusRingElement = document.createElement("span");
     16  const settings = focusableData.settings;
     17  let ringColor = window
     18    .getComputedStyle(document.body, null)
     19    .getPropertyValue("color");
    920
    10 function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
     21  mountElement(focusRingElement);
     22  addClasses(focusRingElement, focusableElements);
     23  addAttributes(focusRingElement, settings);
     24  addEvents();
    1125
    12 function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
     26  function mountElement(focusRingElement) {
     27    let mountToElement = document.body;
     28    const footer = document.querySelector("footer");
     29    if (footer) {
     30      mountToElement = footer;
     31    }
     32    mountToElement.insertAdjacentElement("beforeend", focusRingElement);
     33  }
    1334
    14 function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
     35  function addClasses(focusRingElement, focusableElements) {
     36    focusRingElement.classList.add("focusable__ring");
     37    for (const element of focusableElements) {
     38      element.classList.add("focusable");
     39    }
     40  }
    1541
    16 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
     42  function addAttributes(focusRingElement, settings) {
     43    focusRingElement.setAttribute(
     44      "data-focusable-transition",
     45      settings.transition
     46    );
     47  }
    1748
    18 function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
     49  function addEvents() {
     50    document.addEventListener(
     51      "active-element",
     52      (event) => {
     53        if (focusSource.used("key") === false) {
     54          return;
     55        }
     56        activeElement = event.detail.focus;
     57        updateRingStyle();
     58      },
     59      false
     60    );
     61    document.addEventListener("scroll", () => {
     62      if (focusSource.used("key") === false) {
     63        return;
     64      }
     65      updateRingPosition();
     66    });
     67  }
    1968
    20 function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
    21 
    22 var Focusable = /*#__PURE__*/function () {
    23   function Focusable() {
    24     _classCallCheck(this, Focusable);
    25 
    26     if (!ally.hasOwnProperty("style") || !ally.hasOwnProperty("query")) {
     69  function updateRingStyle() {
     70    if (!isElement(activeElement)) {
    2771      return;
    2872    }
    29 
    30     this.focusSource = ally.style.focusSource();
    31     this.focusableElements = ally.query.focusable();
    32     this.activeElement = ally.event.activeElement();
    33     this.activeElementBoundingRect = {
    34       left: 0,
    35       top: 0,
    36       width: 0,
    37       height: 0
     73    activeElementBoundingRect = activeElement.getBoundingClientRect();
     74    updateRingColor(activeElement, "color");
     75    const style = {
     76      transform: `translate(${activeElementBoundingRect.left}px, ${activeElementBoundingRect.top}px)`,
     77      width: activeElementBoundingRect.width + "px",
     78      height: activeElementBoundingRect.height + "px",
     79      outlineColor: ringColor,
    3880    };
    39     this.focusRingElement = document.createElement("span");
    40     this.settings = focusableData.settings;
    41     this.ringColor = window.getComputedStyle(document.body, null).getPropertyValue("color");
    42     this.mountElement();
    43     this.addClasses();
    44     this.addAttributes();
    45     this.addEvents();
     81    applyStyle(style);
    4682  }
    4783
    48   _createClass(Focusable, [{
    49     key: "mountElement",
    50     value: function mountElement() {
    51       var mountToElement = document.body;
    52       var footer = document.querySelector("footer");
     84  function updateRingColor(element, property) {
     85    if (!isElement(element)) {
     86      return;
     87    }
     88    if (!isElement(element.parentElement)) {
     89      return;
     90    }
     91    let color = window
     92      .getComputedStyle(element.parentElement, null)
     93      .getPropertyValue(property);
     94    if (color === "rgba(0, 0, 0, 0)" && element !== document.body) {
     95      updateRingColor(element.parentElement, "color");
     96    } else {
     97      ringColor = color;
     98    }
     99    return;
     100  }
    53101
    54       if (footer) {
    55         mountToElement = footer;
    56       }
     102  function updateRingPosition() {
     103    if (!isElement(activeElement)) {
     104      return;
     105    }
     106    activeElementBoundingRect = activeElement.getBoundingClientRect();
     107    const style = {
     108      transform: `translate(${activeElementBoundingRect.left}px, ${activeElementBoundingRect.top}px)`,
     109    };
     110    applyStyle(style);
     111  }
    57112
    58       mountToElement.insertAdjacentElement("beforeend", this.focusRingElement);
    59     }
    60   }, {
    61     key: "addClasses",
    62     value: function addClasses() {
    63       this.focusRingElement.classList.add("focusable__ring");
    64 
    65       var _iterator = _createForOfIteratorHelper(this.focusableElements),
    66           _step;
    67 
    68       try {
    69         for (_iterator.s(); !(_step = _iterator.n()).done;) {
    70           var element = _step.value;
    71           element.classList.add("focusable");
    72         }
    73       } catch (err) {
    74         _iterator.e(err);
    75       } finally {
    76         _iterator.f();
     113  function applyStyle(style) {
     114    for (const property in style) {
     115      if (Object.hasOwnProperty.call(style, property)) {
     116        const value = style[property];
     117        focusRingElement.style[property] = value;
    77118      }
    78119    }
    79   }, {
    80     key: "addAttributes",
    81     value: function addAttributes() {
    82       this.focusRingElement.setAttribute("data-focusable-transition", this.settings.transition);
    83     }
    84   }, {
    85     key: "addEvents",
    86     value: function addEvents() {
    87       var _this = this;
     120  }
    88121
    89       document.addEventListener("active-element", function (event) {
    90         if (_this.focusSource.used("key") === false) {
    91           return;
    92         }
    93 
    94         _this.activeElement = event.detail.focus;
    95 
    96         _this.updateRingStyle();
    97       }, false);
    98       document.addEventListener("scroll", function () {
    99         if (_this.focusSource.used("key") === false) {
    100           return;
    101         }
    102 
    103         _this.updateRingPosition();
    104       });
    105     }
    106   }, {
    107     key: "updateRingStyle",
    108     value: function updateRingStyle() {
    109       if (!this.isElement(this.activeElement)) {
    110         return;
    111       }
    112 
    113       this.activeElementBoundingRect = this.activeElement.getBoundingClientRect();
    114       this.updateRingColor(this.activeElement, "color");
    115       var style = {
    116         transform: "translate(".concat(this.activeElementBoundingRect.left, "px, ").concat(this.activeElementBoundingRect.top, "px)"),
    117         width: this.activeElementBoundingRect.width + "px",
    118         height: this.activeElementBoundingRect.height + "px",
    119         outlineColor: this.ringColor
    120       };
    121       this.applyStyle(style);
    122     }
    123   }, {
    124     key: "updateRingColor",
    125     value: function updateRingColor(element, property) {
    126       if (!this.isElement(element)) {
    127         return;
    128       }
    129 
    130       if (!this.isElement(element.parentElement)) {
    131         return;
    132       }
    133 
    134       var color = window.getComputedStyle(element.parentElement, null).getPropertyValue(property);
    135 
    136       if (color === "rgba(0, 0, 0, 0)" && element !== document.body) {
    137         this.updateRingColor(element.parentElement, "color");
    138       } else {
    139         this.ringColor = color;
    140       }
    141 
    142       return;
    143     }
    144   }, {
    145     key: "updateRingPosition",
    146     value: function updateRingPosition() {
    147       if (!this.isElement(this.activeElement)) {
    148         return;
    149       }
    150 
    151       this.activeElementBoundingRect = this.activeElement.getBoundingClientRect();
    152       var style = {
    153         transform: "translate(".concat(this.activeElementBoundingRect.left, "px, ").concat(this.activeElementBoundingRect.top, "px)")
    154       };
    155       this.applyStyle(style);
    156     }
    157   }, {
    158     key: "applyStyle",
    159     value: function applyStyle(style) {
    160       for (var property in style) {
    161         if (Object.hasOwnProperty.call(style, property)) {
    162           var value = style[property];
    163           this.focusRingElement.style[property] = value;
    164         }
    165       }
    166     }
    167   }, {
    168     key: "isElement",
    169     value: function isElement(element) {
    170       return element instanceof Element || element instanceof HTMLDocument;
    171     }
    172   }]);
    173 
    174   return Focusable;
    175 }();
     122  function isElement(element) {
     123    return element instanceof Element || element instanceof HTMLDocument;
     124  }
     125}
    176126
    177127if (typeof jQuery === "function") {
    178128  jQuery(document).ready(function () {
    179     new Focusable();
     129    createFocusable();
    180130  });
    181131} else {
    182132  document.addEventListener("DOMContentLoaded", function () {
    183     new Focusable();
     133    createFocusable();
    184134  });
    185135}
    186 
    187 /***/ }),
    188 
    189 /***/ "./assets/src/sass/style.scss":
    190 /*!************************************!*\
    191   !*** ./assets/src/sass/style.scss ***!
    192   \************************************/
    193 /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
    194 
    195 "use strict";
    196 __webpack_require__.r(__webpack_exports__);
    197 // extracted by mini-css-extract-plugin
    198 
    199 
    200 /***/ })
    201 
    202 /******/    });
    203 /************************************************************************/
    204 /******/    // The module cache
    205 /******/    var __webpack_module_cache__ = {};
    206 /******/   
    207 /******/    // The require function
    208 /******/    function __webpack_require__(moduleId) {
    209 /******/        // Check if module is in cache
    210 /******/        var cachedModule = __webpack_module_cache__[moduleId];
    211 /******/        if (cachedModule !== undefined) {
    212 /******/            return cachedModule.exports;
    213 /******/        }
    214 /******/        // Create a new module (and put it into the cache)
    215 /******/        var module = __webpack_module_cache__[moduleId] = {
    216 /******/            // no module.id needed
    217 /******/            // no module.loaded needed
    218 /******/            exports: {}
    219 /******/        };
    220 /******/   
    221 /******/        // Execute the module function
    222 /******/        __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
    223 /******/   
    224 /******/        // Return the exports of the module
    225 /******/        return module.exports;
    226 /******/    }
    227 /******/   
    228 /******/    // expose the modules object (__webpack_modules__)
    229 /******/    __webpack_require__.m = __webpack_modules__;
    230 /******/   
    231 /************************************************************************/
    232 /******/    /* webpack/runtime/chunk loaded */
    233 /******/    (() => {
    234 /******/        var deferred = [];
    235 /******/        __webpack_require__.O = (result, chunkIds, fn, priority) => {
    236 /******/            if(chunkIds) {
    237 /******/                priority = priority || 0;
    238 /******/                for(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];
    239 /******/                deferred[i] = [chunkIds, fn, priority];
    240 /******/                return;
    241 /******/            }
    242 /******/            var notFulfilled = Infinity;
    243 /******/            for (var i = 0; i < deferred.length; i++) {
    244 /******/                var [chunkIds, fn, priority] = deferred[i];
    245 /******/                var fulfilled = true;
    246 /******/                for (var j = 0; j < chunkIds.length; j++) {
    247 /******/                    if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) {
    248 /******/                        chunkIds.splice(j--, 1);
    249 /******/                    } else {
    250 /******/                        fulfilled = false;
    251 /******/                        if(priority < notFulfilled) notFulfilled = priority;
    252 /******/                    }
    253 /******/                }
    254 /******/                if(fulfilled) {
    255 /******/                    deferred.splice(i--, 1)
    256 /******/                    result = fn();
    257 /******/                }
    258 /******/            }
    259 /******/            return result;
    260 /******/        };
    261 /******/    })();
    262 /******/   
    263 /******/    /* webpack/runtime/hasOwnProperty shorthand */
    264 /******/    (() => {
    265 /******/        __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
    266 /******/    })();
    267 /******/   
    268 /******/    /* webpack/runtime/make namespace object */
    269 /******/    (() => {
    270 /******/        // define __esModule on exports
    271 /******/        __webpack_require__.r = (exports) => {
    272 /******/            if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
    273 /******/                Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    274 /******/            }
    275 /******/            Object.defineProperty(exports, '__esModule', { value: true });
    276 /******/        };
    277 /******/    })();
    278 /******/   
    279 /******/    /* webpack/runtime/jsonp chunk loading */
    280 /******/    (() => {
    281 /******/        // no baseURI
    282 /******/       
    283 /******/        // object to store loaded and loading chunks
    284 /******/        // undefined = chunk not loaded, null = chunk preloaded/prefetched
    285 /******/        // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
    286 /******/        var installedChunks = {
    287 /******/            "/js/main": 0,
    288 /******/            "css/style": 0
    289 /******/        };
    290 /******/       
    291 /******/        // no chunk on demand loading
    292 /******/       
    293 /******/        // no prefetching
    294 /******/       
    295 /******/        // no preloaded
    296 /******/       
    297 /******/        // no HMR
    298 /******/       
    299 /******/        // no HMR manifest
    300 /******/       
    301 /******/        __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0);
    302 /******/       
    303 /******/        // install a JSONP callback for chunk loading
    304 /******/        var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
    305 /******/            var [chunkIds, moreModules, runtime] = data;
    306 /******/            // add "moreModules" to the modules object,
    307 /******/            // then flag all "chunkIds" as loaded and fire callback
    308 /******/            var moduleId, chunkId, i = 0;
    309 /******/            for(moduleId in moreModules) {
    310 /******/                if(__webpack_require__.o(moreModules, moduleId)) {
    311 /******/                    __webpack_require__.m[moduleId] = moreModules[moduleId];
    312 /******/                }
    313 /******/            }
    314 /******/            if(runtime) var result = runtime(__webpack_require__);
    315 /******/            if(parentChunkLoadingFunction) parentChunkLoadingFunction(data);
    316 /******/            for(;i < chunkIds.length; i++) {
    317 /******/                chunkId = chunkIds[i];
    318 /******/                if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
    319 /******/                    installedChunks[chunkId][0]();
    320 /******/                }
    321 /******/                installedChunks[chunkIds[i]] = 0;
    322 /******/            }
    323 /******/            return __webpack_require__.O(result);
    324 /******/        }
    325 /******/       
    326 /******/        var chunkLoadingGlobal = self["webpackChunkfocusable"] = self["webpackChunkfocusable"] || [];
    327 /******/        chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
    328 /******/        chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
    329 /******/    })();
    330 /******/   
    331 /************************************************************************/
    332 /******/   
    333 /******/    // startup
    334 /******/    // Load entry module and return exports
    335 /******/    // This entry module depends on other loaded chunks and execution need to be delayed
    336 /******/    __webpack_require__.O(undefined, ["css/style"], () => (__webpack_require__("./assets/src/js/main.js")))
    337 /******/    var __webpack_exports__ = __webpack_require__.O(undefined, ["css/style"], () => (__webpack_require__("./assets/src/sass/style.scss")))
    338 /******/    __webpack_exports__ = __webpack_require__.O(__webpack_exports__);
    339 /******/   
    340 /******/ })()
    341 ;
  • focusable/trunk/focusable.php

    r2541167 r3306340  
    77 *
    88 * @wordpress-plugin
    9  * Plugin Name:       Focusable
    10  * Plugin URI:        https://khizar.info/#/focusable
    11  * Description:       This plugin displays a ring on the focusable elements when navigating through keyboard.
    12  * Version:           1.3.0
     9 * Plugin Name:       Focusable - Focus Ring On Any Element
     10 * Plugin URI:        https://redoxbird.com/products/focusable
     11 * Description:       Make your website instantly more accessible! Focusable restores and enhances the visible focus ring for keyboard users, ensuring everyone can navigate your site with confidence.
     12 * Version:           2.0.0
    1313 * Author:            Khizar Hasan
    14  * Author URI:        https://khizar.info
     14 * Author URI:        https://redoxbird.com
    1515 * License:           GPL-2.0+
    1616 * License URI:       http://www.gnu.org/licenses/gpl-2.0.txt
     
    2424}
    2525
     26include_once plugin_dir_path(__FILE__) . 'inc/WordPressSettingsFramework.php';
     27
    2628define('FOCUSABLE_VERSION', '1.3.0');
    2729define('FOCUSABLE_DIR_URI', plugin_dir_url(__FILE__));
     
    2931define('FOCUSABLE_DEVELOPMENT_MODE', false);
    3032
    31 if (file_exists(dirname(__FILE__) . '/vendor/autoload.php')):
    32     require_once dirname(__FILE__) . '/vendor/autoload.php';
    33 endif;
    3433
    35 if (class_exists('Focusable\\Init')):
    36     Focusable\Init::register_services();
    37 endif;
     34if (!function_exists('focusable_assets')) {
     35    /**
     36     * Get assets folder url.
     37     *
     38     * @param  string  $path
     39     * @return string
     40     */
     41
     42    function focusable_assets($path)
     43    {
     44        if (!$path) {
     45            return;
     46        }
     47
     48        return FOCUSABLE_DIR_URI . '/assets/dist/' . $path;
     49    }
     50}
     51
     52add_action('wp_enqueue_scripts', 'focusable_enqueue_scripts');
     53
     54function focusable_enqueue_scripts() {
     55    $version = FOCUSABLE_VERSION;
     56
     57    if (FOCUSABLE_DEVELOPMENT_MODE) {
     58        $version = time();
     59    }
     60
     61    wp_enqueue_script('ally', FOCUSABLE_DIR_URI . 'assets/vendor/js/ally.min.js', array(), '1.4.1', true);
     62    wp_enqueue_script('focusable-main', focusable_assets('js/main.js'), array(), $version, true);
     63
     64    wp_localize_script('focusable-main', 'focusableData', [
     65        "settings" => [
     66            "transition" => wpsf_get_setting('focusable_settings_general', 'general', 'transition'),
     67        ],
     68    ]);
     69
     70    wp_enqueue_style('focusable-style', focusable_assets('css/style.css'), array(), $version, 'all');
     71}
     72
     73if (!function_exists('wpsf_get_setting')) {
     74    /**
     75     * Get a setting from an option group
     76     *
     77     * @param string $option_group
     78     * @param string $section_id May also be prefixed with tab ID
     79     * @param string $field_id
     80     *
     81     * @return mixed
     82     */
     83    function wpsf_get_setting($option_group, $section_id, $field_id)
     84    {
     85        $options = get_option($option_group . '_settings');
     86        if (isset($options[$section_id . '_' . $field_id])) {
     87            return $options[$section_id . '_' . $field_id];
     88        }
     89
     90        return false;
     91    }
     92}
     93
     94if (!function_exists('wpsf_delete_settings')) {
     95    /**
     96     * Delete all the saved settings from a settings file/option group
     97     *
     98     * @param string $option_group
     99     */
     100    function wpsf_delete_settings($option_group)
     101    {
     102        delete_option($option_group . '_settings');
     103    }
     104}
     105
     106
     107if (!function_exists('focusable_register_setup')) {
     108    function focusable_register_setup() {
     109        static $wpsf = null;
     110        register_activation_hook(__FILE__, 'focusable_activate');
     111        register_deactivation_hook(__FILE__, 'focusable_deactivate');
     112
     113        if (!$wpsf) {
     114            $wpsf = new WordPressSettingsFramework(FOCUSABLE_DIR_PATH . 'views/admin/index.php', 'focusable_settings_general');
     115        }
     116
     117        add_action('admin_menu', function() use (&$wpsf) {
     118            focusable_add_settings_page($wpsf);
     119        }, 20);
     120
     121        add_filter($wpsf->get_option_group() . '_settings_validate', 'focusable_validate_settings');
     122
     123        add_filter('wpsf_menu_icon_url_focusable_settings_general', function($icon) {
     124            $icon = focusable_assets('images/icon.png');
     125            return $icon;
     126        });
     127    }
     128}
     129
     130if (!function_exists('focusable_activate')) {
     131    function focusable_activate() {
     132        // Activation logic here
     133    }
     134}
     135
     136if (!function_exists('focusable_deactivate')) {
     137    function focusable_deactivate() {
     138        // Deactivation logic here
     139    }
     140}
     141
     142if (!function_exists('focusable_set_locale')) {
     143    function focusable_set_locale() {
     144        load_plugin_textdomain(
     145            'focusable',
     146            false,
     147            dirname(dirname(plugin_basename(__FILE__))) . '/languages/'
     148        );
     149    }
     150}
     151
     152if (!function_exists('focusable_add_settings_page')) {
     153    function focusable_add_settings_page($wpsf) {
     154        $wpsf->add_settings_page(array(
     155            'page_title' => __('Focusable', 'focusable'),
     156            'menu_title' => 'Focusable',
     157            'capability' => 'manage_options',
     158        ));
     159    }
     160}
     161
     162if (!function_exists('focusable_validate_settings')) {
     163    function focusable_validate_settings($input) {
     164        // Do your settings validation here
     165        // Same as $sanitize_callback from http://codex.wordpress.org/Function_Reference/register_setting
     166        return $input;
     167    }
     168}
     169
     170add_action('plugins_loaded', 'focusable_register_setup');
     171
  • focusable/trunk/languages/focusable.pot

    r2526498 r3306340  
     1# Copyright (C) 2025 Khizar Hasan
     2# This file is distributed under the GPL-2.0+.
     3msgid ""
     4msgstr ""
     5"Project-Id-Version: Focusable 2.0.0\n"
     6"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/focusable\n"
     7"Last-Translator: FULL Focusable <EMAIL@ADDRESS>\n"
     8"Language-Team: LANGUAGE <LL@li.org>\n"
     9"MIME-Version: 1.0\n"
     10"Content-Type: text/plain; charset=UTF-8\n"
     11"Content-Transfer-Encoding: 8bit\n"
     12"POT-Creation-Date: 2025-06-04T16:51:08+05:30\n"
     13"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
     14"X-Generator: WP-CLI 2.11.0\n"
     15"X-Domain: focusable\n"
     16
     17#. Plugin Name of the plugin
     18#: focusable.php
     19msgid "Focusable - Focus Ring On Any Element"
     20msgstr ""
     21
     22#. Plugin URI of the plugin
     23#: focusable.php
     24msgid "https://redoxbird.com/products/focusable"
     25msgstr ""
     26
     27#. Description of the plugin
     28#: focusable.php
     29msgid "Make your website instantly more accessible! Focusable restores and enhances the visible focus ring for keyboard users, ensuring everyone can navigate your site with confidence."
     30msgstr ""
     31
     32#. Author of the plugin
     33#: focusable.php
     34msgid "Khizar Hasan"
     35msgstr ""
     36
     37#. Author URI of the plugin
     38#: focusable.php
     39msgid "https://redoxbird.com"
     40msgstr ""
     41
     42#: focusable.php:155
     43msgid "Focusable"
     44msgstr ""
  • focusable/trunk/readme.txt

    r2541167 r3306340  
    1 === Focusable ===
    2 Contributors: khizar
    3 Tags: accessibility, focus outline, keyboard navigation
     1=== Focusable - Focus Ring On Any Element ===
     2Contributors: RedOxbird
     3Tags: accessibility, focus outline, keyboard navigation, WCAG, usability, a11y
    44Requires at least: 5.0.0
    5 Tested up to: 5.7.1
    6 Stable tag: 1.3.0
     5Tested up to: 6.8
     6Stable tag: 2.0.0
    77License: GPLv2 or later
    88License URI: http://www.gnu.org/licenses/gpl-2.0.html
    99
    10 Displays a ring around the focusable elements when navigating through keyboard.
     10Make your website instantly more accessible! Focusable restores and enhances the visible focus ring for keyboard users, ensuring everyone can navigate your site with confidence.
    1111
    1212== Description ==
    13 A WordPress plugin that displays a ring on the focusable elements when navigating through keyboard.
     13Focusable is a lightweight, plug-and-play WordPress plugin that displays a beautiful, customizable focus ring around interactive elements as users navigate with the keyboard. Many themes remove or hide the default focus outline, making it difficult for keyboard and assistive technology users to know where they are on the page. Focusable solves this problem in seconds—no coding required.
     14
     15**Key Benefits:**
     16- Instantly improves accessibility and meets WCAG guidelines.
     17- Helps users with disabilities, power users, and anyone who prefers keyboard navigation.
     18- Works with any theme, even if the default focus outline is removed.
     19- Customizable transition and appearance via plugin settings.
     20
     21== Features ==
     22- Adds a visible focus ring to all focusable elements (links, buttons, form fields, etc.)
     23- Keyboard navigation support out of the box
     24- Customizable ring style and transition
     25- Lightweight and fast—no bloat
     26- Compatible with all major browsers and themes
     27- Developer-friendly: extend or style as needed
    1428
    1529== Installation ==
     
    20342. **Search** for "Focusable"
    21353. **Activate** Focusable from your Plugins page
     364. (Optional) Adjust settings in the admin area to customize the focus ring appearance.
    2237
    2338== Frequently Asked Questions ==
    2439
    25 = Who is this plugin for exactly? =
     40= Who should use Focusable? =
     41Anyone who wants to make their website more accessible, especially for keyboard and screen reader users. It’s essential for site owners, agencies, and developers who care about usability and compliance.
    2642
    27 This plugin is for those who are unable to see the focus ring on elements when navigating using the keyboard due to having been removed by the theme developer.
     43= Will this work with my theme? =
     44Yes! Focusable is designed to work with any WordPress theme, even if the theme removes the default focus outline.
     45
     46= Is it customizable? =
     47Yes, you can adjust the transition and appearance of the focus ring from the plugin settings.
    2848
    2949== Screenshots ==
     501. Example of the focus ring on a button.
     512. Focus ring on a form input.
     523. Keyboard navigation highlighting links.
    3053
    3154== Changelog ==
    3255
     56= 2.0.0 =
     57* Tested up to WordPress 6.8
     58
    3359= 1.3.0 =
    34 * Testing up to WordPress 5.7.2.
     60* Tested up to WordPress 6.5.0.
     61* Improved compatibility and accessibility.
    3562
    3663= 1.2.0 =
     
    4168
    4269= 1.0.0 =
    43 * Releasing the first version of the plugin.
     70* Initial release.
     71
     72== Upgrade Notice ==
     73
     74= 2.0.0 =
     75Recommended update for compatibility and accessibility improvements.
     76
     77== Credits ==
     78
     79Developed by Khizar Hasan. Inspired by the need for a more accessible web.
     80
     81== License ==
     82
     83This plugin is licensed under the GPLv2 or later.
Note: See TracChangeset for help on using the changeset viewer.