Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
File Manager
/
portableapps
/
assets
:
themestrap.plugin.toolbar.js
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
// Toolbar (((themestrap = {}, $) => { const instanceName = '__pluginToolbar'; const STYLE_ID = 'ts-toolbar-styles'; const CSS_TEXT = `/* Themestrap — PluginToolbar */ .ts-toolbar { --ts-tb-bg: #f4f6f8; --ts-tb-border: rgba(0,0,0,.1); --ts-tb-shadow: 0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.05); --ts-tb-pad: 4px; --ts-tb-gap: 2px; --ts-tb-radius: 8px; --ts-tb-item-h: 34px; --ts-tb-item-px: 10px; --ts-tb-item-radius: 5px; --ts-tb-item-fg: #374151; --ts-tb-item-hover-bg: rgba(0,0,0,.06); --ts-tb-item-hover-fg: #111827; --ts-tb-item-active-bg: rgba(0,0,0,.09); --ts-tb-item-font: 0.875rem; --ts-tb-on-bg: #e8672a; --ts-tb-on-fg: #fff; --ts-tb-on-hover-bg: #d2541b; --ts-tb-focus-ring: #e8672a; --ts-tb-focus-offset: 2px; --ts-tb-sep-color: rgba(0,0,0,.12); --ts-tb-sep-size: 1px; --ts-tb-sep-margin: 4px; --ts-tb-group-bg: rgba(0,0,0,.05); --ts-tb-group-radius: 6px; --ts-tb-group-gap: 1px; --ts-tb-disabled-opacity: .4; } /* Dark theme — forced via class or auto via html.dark */ .ts-toolbar--dark, html.dark .ts-toolbar:not(.ts-toolbar--light) { --ts-tb-bg: #0e2238; --ts-tb-border: rgba(255,255,255,.08); --ts-tb-shadow: 0 2px 8px rgba(0,0,0,.45); --ts-tb-item-fg: rgba(255,255,255,.72); --ts-tb-item-hover-bg: rgba(255,255,255,.08); --ts-tb-item-hover-fg: #fff; --ts-tb-item-active-bg: rgba(255,255,255,.12); --ts-tb-on-bg: #2ab8c8; --ts-tb-on-fg: #0a1929; --ts-tb-on-hover-bg: #1e9aaa; --ts-tb-focus-ring: #2ab8c8; --ts-tb-sep-color: rgba(255,255,255,.1); --ts-tb-group-bg: rgba(255,255,255,.06); } /* Root */ .ts-toolbar { display : inline-flex; align-items : center; flex-wrap : nowrap; gap : var(--ts-tb-gap); padding : var(--ts-tb-pad); background : var(--ts-tb-bg); border : 1px solid var(--ts-tb-border); border-radius : var(--ts-tb-radius); box-shadow : var(--ts-tb-shadow); width : max-content; max-width : 100%; } .ts-toolbar--vertical { flex-direction : column; align-items : stretch; width : max-content; } .ts-toolbar--full { width : 100%; flex-wrap : wrap; } /* Shared item base */ .ts-toolbar [data-toolbar-button], .ts-toolbar [data-toolbar-link], .ts-toolbar [data-toolbar-toggle-item] { display : inline-flex; align-items : center; justify-content : center; gap : 6px; height : var(--ts-tb-item-h); padding : 0 var(--ts-tb-item-px); font-size : var(--ts-tb-item-font); font-weight : 500; line-height : 1; white-space : nowrap; border : 0; border-radius : var(--ts-tb-item-radius); background : transparent; color : var(--ts-tb-item-fg); cursor : pointer; text-decoration : none; transition : background-color .12s ease, color .12s ease, opacity .12s ease; user-select : none; -webkit-user-select: none; outline : none; flex-shrink : 0; } /* Icon-only button — narrow */ .ts-toolbar [data-toolbar-button].ts-tb-icon, .ts-toolbar [data-toolbar-toggle-item].ts-tb-icon { width : var(--ts-tb-item-h); padding : 0; } /* Hover */ .ts-toolbar [data-toolbar-button]:hover:not([data-toolbar-disabled]), .ts-toolbar [data-toolbar-link]:hover:not([data-toolbar-disabled]), .ts-toolbar [data-toolbar-toggle-item]:hover:not([data-toolbar-disabled]) { background : var(--ts-tb-item-hover-bg); color : var(--ts-tb-item-hover-fg); } /* Focus */ .ts-toolbar [data-toolbar-button]:focus-visible, .ts-toolbar [data-toolbar-link]:focus-visible, .ts-toolbar [data-toolbar-toggle-item]:focus-visible { outline : 2px solid var(--ts-tb-focus-ring); outline-offset : var(--ts-tb-focus-offset); } /* Active click flash */ .ts-toolbar [data-toolbar-button]:active:not([data-toolbar-disabled]), .ts-toolbar [data-toolbar-link]:active:not([data-toolbar-disabled]) { background : var(--ts-tb-item-active-bg); } /* Pressed / selected toggle item */ .ts-toolbar [data-toolbar-toggle-item].ts-toolbar__toggle-item--on, .ts-toolbar [data-toolbar-toggle-item][aria-pressed="true"] { background : var(--ts-tb-on-bg); color : var(--ts-tb-on-fg); } .ts-toolbar [data-toolbar-toggle-item].ts-toolbar__toggle-item--on:hover:not([data-toolbar-disabled]), .ts-toolbar [data-toolbar-toggle-item][aria-pressed="true"]:hover:not([data-toolbar-disabled]) { background : var(--ts-tb-on-hover-bg); color : var(--ts-tb-on-fg); } /* Disabled */ .ts-toolbar [data-toolbar-button][data-toolbar-disabled], .ts-toolbar [data-toolbar-link][data-toolbar-disabled], .ts-toolbar [data-toolbar-toggle-item][data-toolbar-disabled] { opacity : var(--ts-tb-disabled-opacity); cursor : not-allowed; pointer-events : none; } /* Toggle group container */ .ts-toolbar [data-toolbar-toggle-group] { display : inline-flex; align-items : center; gap : var(--ts-tb-group-gap); background : var(--ts-tb-group-bg); border-radius : var(--ts-tb-group-radius); padding : 2px; flex-shrink : 0; } .ts-toolbar--vertical [data-toolbar-toggle-group] { flex-direction : column; align-items : stretch; } /* Tighten radii on interior items within a group */ .ts-toolbar [data-toolbar-toggle-group] [data-toolbar-toggle-item]:not(:first-child):not(:last-child) { border-radius : 3px; } .ts-toolbar [data-toolbar-toggle-group] [data-toolbar-toggle-item]:first-child:not(:last-child) { border-bottom-right-radius : 3px; border-top-right-radius : 3px; } .ts-toolbar [data-toolbar-toggle-group] [data-toolbar-toggle-item]:last-child:not(:first-child) { border-top-left-radius : 3px; border-bottom-left-radius : 3px; } /* Separator */ .ts-toolbar [data-toolbar-separator] { flex-shrink : 0; align-self : stretch; width : var(--ts-tb-sep-size); background : var(--ts-tb-sep-color); border : none; margin : var(--ts-tb-sep-margin) var(--ts-tb-sep-margin); } .ts-toolbar--vertical [data-toolbar-separator] { align-self : auto; width : auto; height : var(--ts-tb-sep-size); margin : var(--ts-tb-sep-margin) var(--ts-tb-sep-margin); } /* Link item */ .ts-toolbar [data-toolbar-link] { color : var(--ts-tb-item-fg); } /* SVG icons inside items inherit color and scale to text size */ .ts-toolbar [data-toolbar-button] svg, .ts-toolbar [data-toolbar-link] svg, .ts-toolbar [data-toolbar-toggle-item] svg { width : 1em; height : 1em; flex-shrink : 0; pointer-events: none; } /* Size modifiers on root */ .ts-toolbar--sm { --ts-tb-item-h : 28px; --ts-tb-item-px : 8px; --ts-tb-item-font: 0.8125rem; --ts-tb-pad : 3px; } .ts-toolbar--lg { --ts-tb-item-h : 40px; --ts-tb-item-px : 14px; --ts-tb-item-font: 0.9375rem; --ts-tb-pad : 5px; } @media (prefers-reduced-motion: reduce) { .ts-toolbar [data-toolbar-button], .ts-toolbar [data-toolbar-link], .ts-toolbar [data-toolbar-toggle-item] { transition: none; } }`; function injectStyles() { if (document.getElementById(STYLE_ID)) return; const style = document.createElement('style'); style.id = STYLE_ID; style.textContent = CSS_TEXT; (document.head || document.documentElement).appendChild(style); } function ejectStyles() { const el = document.getElementById(STYLE_ID); if (el) el.parentNode.removeChild(el); } let _seq = 0; const uid = prefix => `${prefix}-${++_seq}-${Math.random().toString(36).slice(2, 7)}`; class PluginToolbar { constructor($el, opts) { return this.initialize($el, opts); } initialize($el, opts) { if ($el.data(instanceName)) return this; this.$el = $el; this._uid = uid('ts-tb'); this._initialHTML = $el.html(); this._focusedIdx = 0; this .setData() .setOptions(opts) .build() .events(); return this; } setData() { this.$el.data(instanceName, this); return this; } setOptions(opts) { const pluginOptions = themestrap.fn.getOptions(this.$el.data('plugin-options')); this.options = $.extend(true, {}, PluginToolbar.defaults, pluginOptions, opts, { wrapper: this.$el }); return this; } build() { injectStyles(); PluginToolbar.instances++; const self = this; const $el = self.$el; const o = self.options; /* Root ARIA */ $el.addClass('ts-toolbar'); if (!$el.attr('role')) $el.attr('role', 'toolbar'); if (!$el.attr('aria-label') && o.ariaLabel) $el.attr('aria-label', o.ariaLabel); $el.attr('aria-orientation', o.orientation); if (o.dir) $el.attr('dir', o.dir); if (o.orientation === 'vertical') $el.addClass('ts-toolbar--vertical'); if (o.size === 'sm') $el.addClass('ts-toolbar--sm'); if (o.size === 'lg') $el.addClass('ts-toolbar--lg'); if (o.dark) $el.addClass('ts-toolbar--dark'); if (o.full) $el.addClass('ts-toolbar--full'); /* Separators */ $el.find('[data-toolbar-separator]').each(function() { const $sep = $(this); $sep.attr('role', 'separator'); $sep.attr('aria-orientation', o.orientation === 'horizontal' ? 'vertical' : 'horizontal' ); }); /* Buttons */ $el.find('[data-toolbar-button]').each(function() { const $btn = $(this); if ($btn.is('button') && !$btn.attr('type')) $btn.attr('type', 'button'); }); /* Toggle groups */ $el.find('[data-toolbar-toggle-group]').each(function() { const $group = $(this); const type = $group.attr('data-toolbar-type') || 'single'; $group.data('ts-tb-type', type); const groupLabel = $group.attr('data-toolbar-group-label'); if (groupLabel && !$group.attr('aria-label')) { $group.attr('role', 'group').attr('aria-label', groupLabel); } else { $group.attr('role', 'group'); } $group.find('[data-toolbar-toggle-item]').each(function() { const $item = $(this); const isPressed = $item.attr('data-toolbar-pressed') === 'true'; if ($item.is('button') && !$item.attr('type')) $item.attr('type', 'button'); $item.attr('aria-pressed', isPressed ? 'true' : 'false'); $item.toggleClass('ts-toolbar__toggle-item--on', isPressed); }); }); /* Disabled ARIA */ $el.find('[data-toolbar-disabled]').attr('aria-disabled', 'true').attr('tabindex', '-1'); /* Roving tabindex — all items get -1 except the first */ self._applyRovingTabindex(); return this; } _items() { /* All interactive toolbar items, excluding disabled and hidden */ return this.$el.find([ '[data-toolbar-button]:not([data-toolbar-disabled])', '[data-toolbar-link]:not([data-toolbar-disabled])', '[data-toolbar-toggle-item]:not([data-toolbar-disabled])' ].join(',')); } _applyRovingTabindex() { const $items = this._items(); $items.attr('tabindex', '-1'); const idx = Math.min(this._focusedIdx, $items.length - 1); if ($items.eq(idx).length) { $items.eq(idx).attr('tabindex', '0'); } else if ($items.length) { $items.first().attr('tabindex', '0'); this._focusedIdx = 0; } } _moveFocus(delta) { const $items = this._items(); if (!$items.length) return; const $focused = $items.filter(':focus'); let idx = $items.index($focused); if (idx < 0) idx = this._focusedIdx; const o = this.options; let next = idx + delta; if (o.loop) { next = ((next % $items.length) + $items.length) % $items.length; } else { next = Math.max(0, Math.min($items.length - 1, next)); } $items.attr('tabindex', '-1'); $items.eq(next).attr('tabindex', '0').trigger('focus'); this._focusedIdx = next; } _focusAt(idx) { const $items = this._items(); if (!$items.length) return; const target = Math.max(0, Math.min($items.length - 1, idx)); $items.attr('tabindex', '-1'); $items.eq(target).attr('tabindex', '0').trigger('focus'); this._focusedIdx = target; } events() { const self = this; const $el = self.$el; const o = self.options; const ns = `.toolbar.${self._uid}`; const isHoriz = () => self.options.orientation === 'horizontal'; const isRtl = () => self.options.dir === 'rtl'; /* Keyboard navigation — roving tabindex + arrow keys */ $el.on(`keydown${ns}`, function(e) { const key = e.key; const horiz = isHoriz(); const rtl = isRtl(); /* Determine prev / next axis keys */ const prevKey = horiz ? (rtl ? 'ArrowRight' : 'ArrowLeft') : 'ArrowUp'; const nextKey = horiz ? (rtl ? 'ArrowLeft' : 'ArrowRight') : 'ArrowDown'; if (key === prevKey) { e.preventDefault(); self._moveFocus(-1); } else if (key === nextKey) { e.preventDefault(); self._moveFocus(1); } else if (key === 'Home') { e.preventDefault(); self._focusAt(0); } else if (key === 'End') { e.preventDefault(); self._focusAt(self._items().length - 1); } }); /* Maintain roving tabindex on focusin */ $el.on(`focusin${ns}`, function(e) { const $items = self._items(); const $target = $(e.target); const idx = $items.index($target); if (idx >= 0) { $items.attr('tabindex', '-1'); $target.attr('tabindex', '0'); self._focusedIdx = idx; } }); /* Toggle item clicks */ $el.on(`click${ns}`, '[data-toolbar-toggle-item]', function(e) { const $item = $(this); if ($item.is('[data-toolbar-disabled]')) return; const $group = $item.closest('[data-toolbar-toggle-group]'); const type = $group.data('ts-tb-type') || 'single'; const value = $item.attr('data-toolbar-value') || null; if (type === 'single') { const wasOn = $item.attr('aria-pressed') === 'true'; /* Deselect all siblings first */ $group.find('[data-toolbar-toggle-item]') .attr('aria-pressed', 'false') .removeClass('ts-toolbar__toggle-item--on'); /* Select this one unless it was already on (allow deselect when o.allowDeselect) */ if (!wasOn || o.allowDeselect) { const newState = !wasOn; $item.attr('aria-pressed', newState ? 'true' : 'false') .toggleClass('ts-toolbar__toggle-item--on', newState); } else { $item.attr('aria-pressed', 'true') .addClass('ts-toolbar__toggle-item--on'); } } else { /* Multiple — toggle independently */ const wasOn = $item.attr('aria-pressed') === 'true'; const newState = !wasOn; $item.attr('aria-pressed', newState ? 'true' : 'false') .toggleClass('ts-toolbar__toggle-item--on', newState); } const pressed = $item.attr('aria-pressed') === 'true'; /* Fire native CustomEvent (no jQuery re-entry loop) */ $el[0].dispatchEvent(new CustomEvent('toolbar:toggle', { bubbles : true, detail : { value, pressed, item: $item[0], group: $group[0], instance: self } })); /* jQuery event for backward compatibility */ $el.trigger('toolbar:toggle', [value, pressed, $item, $group, self]); if ($.isFunction(o.onToggle)) { o.onToggle.call(self, value, pressed, $group[0]); } }); /* Button clicks */ $el.on(`click${ns}`, '[data-toolbar-button]', function() { const $btn = $(this); if ($btn.is('[data-toolbar-disabled]')) return; const value = $btn.attr('data-toolbar-value') || null; $el[0].dispatchEvent(new CustomEvent('toolbar:button', { bubbles : true, detail : { value, button: $btn[0], instance: self } })); $el.trigger('toolbar:button', [value, $btn, self]); if ($.isFunction(o.onButton)) { o.onButton.call(self, value, $btn[0]); } }); /* Link clicks */ $el.on(`click${ns}`, '[data-toolbar-link]', function() { const $link = $(this); if ($link.is('[data-toolbar-disabled]')) return; const value = $link.attr('data-toolbar-value') || $link.attr('href') || null; $el[0].dispatchEvent(new CustomEvent('toolbar:link', { bubbles : true, detail : { value, link: $link[0], instance: self } })); $el.trigger('toolbar:link', [value, $link, self]); if ($.isFunction(o.onLink)) { o.onLink.call(self, value, $link[0]); } }); return this; } /* ── Public API ─────────────────────────────────── */ /** * Get the current selected value(s) for a toggle group. * @param {string|Element|jQuery} [groupOrSelector] - target group (defaults to first) * @returns {string|string[]|null} */ getValues(groupOrSelector) { const $group = groupOrSelector ? $(groupOrSelector).is('[data-toolbar-toggle-group]') ? $(groupOrSelector) : $(groupOrSelector).closest('[data-toolbar-toggle-group]') : this.$el.find('[data-toolbar-toggle-group]').first(); const type = $group.data('ts-tb-type') || 'single'; const $on = $group.find('[data-toolbar-toggle-item][aria-pressed="true"]'); const vals = $on.map(function() { return $(this).attr('data-toolbar-value') || null; }).get().filter(Boolean); return type === 'single' ? (vals[0] || null) : vals; } /** * Programmatically set a value in a toggle group. * @param {string} value * @param {string|Element|jQuery} [groupOrSelector] * @returns {this} */ setValue(value, groupOrSelector) { const $group = groupOrSelector ? $(groupOrSelector).is('[data-toolbar-toggle-group]') ? $(groupOrSelector) : $(groupOrSelector).closest('[data-toolbar-toggle-group]') : this.$el.find('[data-toolbar-toggle-group]').first(); const $item = $group.find(`[data-toolbar-toggle-item][data-toolbar-value="${value}"]`); if ($item.length) $item.trigger('click'); return this; } /** * Set multiple values for a multiple-type toggle group. * @param {string[]} values * @param {string|Element|jQuery} [groupOrSelector] * @returns {this} */ setValues(values, groupOrSelector) { const $group = groupOrSelector ? $(groupOrSelector).is('[data-toolbar-toggle-group]') ? $(groupOrSelector) : $(groupOrSelector).closest('[data-toolbar-toggle-group]') : this.$el.find('[data-toolbar-toggle-group]').first(); if ($group.data('ts-tb-type') !== 'multiple') return this; /* Clear first */ $group.find('[data-toolbar-toggle-item]') .attr('aria-pressed', 'false') .removeClass('ts-toolbar__toggle-item--on'); /* Set each */ values.forEach(val => { $group.find(`[data-toolbar-toggle-item][data-toolbar-value="${val}"]`) .attr('aria-pressed', 'true') .addClass('ts-toolbar__toggle-item--on'); }); return this; } /** * Clear all selections in a toggle group. * @param {string|Element|jQuery} [groupOrSelector] * @returns {this} */ clearGroup(groupOrSelector) { const $group = groupOrSelector ? $(groupOrSelector).is('[data-toolbar-toggle-group]') ? $(groupOrSelector) : $(groupOrSelector).closest('[data-toolbar-toggle-group]') : this.$el.find('[data-toolbar-toggle-group]').first(); $group.find('[data-toolbar-toggle-item]') .attr('aria-pressed', 'false') .removeClass('ts-toolbar__toggle-item--on'); return this; } /** * Disable one or more items by value or selector. * @param {string} selectorOrValue * @returns {this} */ disable(selectorOrValue) { const $target = this.$el.find( `[data-toolbar-value="${selectorOrValue}"], ${selectorOrValue}` ); $target.attr('data-toolbar-disabled', 'true') .attr('aria-disabled', 'true') .attr('tabindex', '-1'); this._applyRovingTabindex(); return this; } /** * Enable one or more disabled items. * @param {string} selectorOrValue * @returns {this} */ enable(selectorOrValue) { const $target = this.$el.find( `[data-toolbar-value="${selectorOrValue}"], ${selectorOrValue}` ); $target.removeAttr('data-toolbar-disabled') .removeAttr('aria-disabled'); this._applyRovingTabindex(); return this; } /** * Move focus to the toolbar (first or last-focused item). * @returns {this} */ focus() { this._focusAt(this._focusedIdx); return this; } destroy() { const self = this; const $el = self.$el; $el.off(`.toolbar.${self._uid}`); $el.html(self._initialHTML); $el.removeAttr('role aria-label aria-orientation dir') .removeClass('ts-toolbar ts-toolbar--vertical ts-toolbar--dark ts-toolbar--sm ts-toolbar--lg ts-toolbar--full'); $el.removeData(instanceName); PluginToolbar.instances--; if (PluginToolbar.instances <= 0) { PluginToolbar.instances = 0; ejectStyles(); } return this; } } PluginToolbar.instances = 0; PluginToolbar.defaults = { orientation : 'horizontal', // 'horizontal' | 'vertical' loop : true, // wrap arrow-key navigation at edges dir : 'ltr', // 'ltr' | 'rtl' ariaLabel : 'Toolbar', // aria-label on root size : null, // null | 'sm' | 'lg' dark : false, // force dark theme full : false, // stretch to 100% width allowDeselect : false, // single-type: allow deselecting the active item onToggle : null, // fn(value, pressed, groupEl) onButton : null, // fn(value, buttonEl) onLink : null, // fn(value, linkEl) }; $.extend(themestrap, { PluginToolbar }); $.fn.themestrapPluginToolbar = function(opts) { return this.each(function() { const $this = $(this); const instance = $this.data(instanceName); if (typeof opts === 'string') { if (instance && $.isFunction(instance[opts])) { const args = Array.prototype.slice.call(arguments, 1); instance[opts].apply(instance, args); } return; } if (!instance) { new PluginToolbar($this, opts); } }); }; })).apply(this, [window.themestrap, jQuery]);