Changeset 3306340
- Timestamp:
- 06/04/2025 11:22:36 AM (10 months ago)
- Location:
- focusable/trunk
- Files:
-
- 3 added
- 5 edited
-
assets/dist/css/style.css (modified) (2 diffs)
-
assets/dist/js/helpers.js (added)
-
assets/dist/js/main.js (modified) (1 diff)
-
focusable.php (modified) (3 diffs)
-
inc/WordPressSettingsFramework.php (added)
-
languages/focusable.pot (modified) (1 diff)
-
readme.txt (modified) (3 diffs)
-
webpack.mix.js (added)
Legend:
- Unmodified
- Added
- Removed
-
focusable/trunk/assets/dist/css/style.css
r2526498 r3306340 1 /*2 ===================3 Easing4 ===================5 */6 1 .focus-source-key .focusable:focus { 7 2 outline: none; 8 3 box-shadow: none; 9 4 } 5 10 6 .focus-source-key .focusable__ring { 11 7 all: unset; … … 26 22 outline-offset: 2px; 27 23 } 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"] { 29 27 transition: all 400ms cubic-bezier(0.19, 1, 0.22, 1); 30 28 } -
focusable/trunk/assets/dist/js/main.js
r2526498 r3306340 1 /******/ (() => { // webpackBootstrap 2 /******/ var __webpack_modules__ = ({ 1 function createFocusable() { 2 if (!ally.hasOwnProperty("style") || !ally.hasOwnProperty("query")) { 3 return; 4 } 3 5 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"); 9 20 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(); 11 25 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 } 13 34 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 } 15 41 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 } 17 48 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 } 19 68 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)) { 27 71 return; 28 72 } 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, 38 80 }; 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); 46 82 } 47 83 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 } 53 101 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 } 57 112 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; 77 118 } 78 119 } 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 } 88 121 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 } 176 126 177 127 if (typeof jQuery === "function") { 178 128 jQuery(document).ready(function () { 179 newFocusable();129 createFocusable(); 180 130 }); 181 131 } else { 182 132 document.addEventListener("DOMContentLoaded", function () { 183 newFocusable();133 createFocusable(); 184 134 }); 185 135 } 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-plugin198 199 200 /***/ })201 202 /******/ });203 /************************************************************************/204 /******/ // The module cache205 /******/ var __webpack_module_cache__ = {};206 /******/207 /******/ // The require function208 /******/ function __webpack_require__(moduleId) {209 /******/ // Check if module is in cache210 /******/ 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 needed217 /******/ // no module.loaded needed218 /******/ exports: {}219 /******/ };220 /******/221 /******/ // Execute the module function222 /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);223 /******/224 /******/ // Return the exports of the module225 /******/ 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 exports271 /******/ __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 baseURI282 /******/283 /******/ // object to store loaded and loading chunks284 /******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched285 /******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded286 /******/ var installedChunks = {287 /******/ "/js/main": 0,288 /******/ "css/style": 0289 /******/ };290 /******/291 /******/ // no chunk on demand loading292 /******/293 /******/ // no prefetching294 /******/295 /******/ // no preloaded296 /******/297 /******/ // no HMR298 /******/299 /******/ // no HMR manifest300 /******/301 /******/ __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0);302 /******/303 /******/ // install a JSONP callback for chunk loading304 /******/ 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 callback308 /******/ 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 /******/ // startup334 /******/ // Load entry module and return exports335 /******/ // This entry module depends on other loaded chunks and execution need to be delayed336 /******/ __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 7 7 * 8 8 * @wordpress-plugin 9 * Plugin Name: Focusable 10 * Plugin URI: https:// khizar.info/#/focusable11 * Description: This plugin displays a ring on the focusable elements when navigating through keyboard.12 * Version: 1.3.09 * 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 13 13 * Author: Khizar Hasan 14 * Author URI: https:// khizar.info14 * Author URI: https://redoxbird.com 15 15 * License: GPL-2.0+ 16 16 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt … … 24 24 } 25 25 26 include_once plugin_dir_path(__FILE__) . 'inc/WordPressSettingsFramework.php'; 27 26 28 define('FOCUSABLE_VERSION', '1.3.0'); 27 29 define('FOCUSABLE_DIR_URI', plugin_dir_url(__FILE__)); … … 29 31 define('FOCUSABLE_DEVELOPMENT_MODE', false); 30 32 31 if (file_exists(dirname(__FILE__) . '/vendor/autoload.php')):32 require_once dirname(__FILE__) . '/vendor/autoload.php';33 endif;34 33 35 if (class_exists('Focusable\\Init')): 36 Focusable\Init::register_services(); 37 endif; 34 if (!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 52 add_action('wp_enqueue_scripts', 'focusable_enqueue_scripts'); 53 54 function 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 73 if (!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 94 if (!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 107 if (!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 130 if (!function_exists('focusable_activate')) { 131 function focusable_activate() { 132 // Activation logic here 133 } 134 } 135 136 if (!function_exists('focusable_deactivate')) { 137 function focusable_deactivate() { 138 // Deactivation logic here 139 } 140 } 141 142 if (!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 152 if (!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 162 if (!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 170 add_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+. 3 msgid "" 4 msgstr "" 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 19 msgid "Focusable - Focus Ring On Any Element" 20 msgstr "" 21 22 #. Plugin URI of the plugin 23 #: focusable.php 24 msgid "https://redoxbird.com/products/focusable" 25 msgstr "" 26 27 #. Description of the plugin 28 #: focusable.php 29 msgid "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." 30 msgstr "" 31 32 #. Author of the plugin 33 #: focusable.php 34 msgid "Khizar Hasan" 35 msgstr "" 36 37 #. Author URI of the plugin 38 #: focusable.php 39 msgid "https://redoxbird.com" 40 msgstr "" 41 42 #: focusable.php:155 43 msgid "Focusable" 44 msgstr "" -
focusable/trunk/readme.txt
r2541167 r3306340 1 === Focusable ===2 Contributors: khizar3 Tags: accessibility, focus outline, keyboard navigation 1 === Focusable - Focus Ring On Any Element === 2 Contributors: RedOxbird 3 Tags: accessibility, focus outline, keyboard navigation, WCAG, usability, a11y 4 4 Requires at least: 5.0.0 5 Tested up to: 5.7.16 Stable tag: 1.3.05 Tested up to: 6.8 6 Stable tag: 2.0.0 7 7 License: GPLv2 or later 8 8 License URI: http://www.gnu.org/licenses/gpl-2.0.html 9 9 10 Displays a ring around the focusable elements when navigating through keyboard.10 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. 11 11 12 12 == Description == 13 A WordPress plugin that displays a ring on the focusable elements when navigating through keyboard. 13 Focusable 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 14 28 15 29 == Installation == … … 20 34 2. **Search** for "Focusable" 21 35 3. **Activate** Focusable from your Plugins page 36 4. (Optional) Adjust settings in the admin area to customize the focus ring appearance. 22 37 23 38 == Frequently Asked Questions == 24 39 25 = Who is this plugin for exactly? = 40 = Who should use Focusable? = 41 Anyone 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. 26 42 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? = 44 Yes! Focusable is designed to work with any WordPress theme, even if the theme removes the default focus outline. 45 46 = Is it customizable? = 47 Yes, you can adjust the transition and appearance of the focus ring from the plugin settings. 28 48 29 49 == Screenshots == 50 1. Example of the focus ring on a button. 51 2. Focus ring on a form input. 52 3. Keyboard navigation highlighting links. 30 53 31 54 == Changelog == 32 55 56 = 2.0.0 = 57 * Tested up to WordPress 6.8 58 33 59 = 1.3.0 = 34 * Testing up to WordPress 5.7.2. 60 * Tested up to WordPress 6.5.0. 61 * Improved compatibility and accessibility. 35 62 36 63 = 1.2.0 = … … 41 68 42 69 = 1.0.0 = 43 * Releasing the first version of the plugin. 70 * Initial release. 71 72 == Upgrade Notice == 73 74 = 2.0.0 = 75 Recommended update for compatibility and accessibility improvements. 76 77 == Credits == 78 79 Developed by Khizar Hasan. Inspired by the need for a more accessible web. 80 81 == License == 82 83 This plugin is licensed under the GPLv2 or later.
Note: See TracChangeset
for help on using the changeset viewer.