@sekiui/elements 0.0.56 → 0.0.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/index.js +1053 -91
- package/dist/cdn/p-BJCq8m2o.js +138 -0
- package/dist/cdn/p-BfRJQMIU.js +111 -0
- package/dist/cdn/{p-bMBhrs0a.js → p-Bp7tjKwQ.js} +427 -7
- package/dist/cdn/seki-button.js +1 -1
- package/dist/cdn/seki-card-action.js +1 -1
- package/dist/cdn/seki-card-content.js +1 -1
- package/dist/cdn/seki-card-description.js +1 -1
- package/dist/cdn/seki-card-footer.js +1 -1
- package/dist/cdn/seki-card-header.js +1 -1
- package/dist/cdn/seki-card-title.js +1 -1
- package/dist/cdn/seki-card.js +1 -1
- package/dist/cdn/seki-field-description.js +1 -1
- package/dist/cdn/seki-field-error.js +1 -1
- package/dist/cdn/seki-field-group.js +1 -1
- package/dist/cdn/seki-field-label.js +1 -1
- package/dist/cdn/seki-field-legend.js +1 -1
- package/dist/cdn/seki-field.js +1 -1
- package/dist/cdn/seki-fieldset.js +1 -1
- package/dist/cdn/seki-input.js +1 -1
- package/dist/cdn/seki-select-content.js +1 -1
- package/dist/cdn/seki-select-group.js +1 -1
- package/dist/cdn/seki-select-option.js +1 -1
- package/dist/cdn/seki-select-trigger.js +1 -1
- package/dist/cdn/seki-select.js +1 -1
- package/dist/cdn/seki-sidebar-content.d.ts +11 -0
- package/dist/cdn/seki-sidebar-content.js +38 -0
- package/dist/cdn/seki-sidebar-footer.d.ts +11 -0
- package/dist/cdn/seki-sidebar-footer.js +38 -0
- package/dist/cdn/seki-sidebar-group.d.ts +11 -0
- package/dist/cdn/seki-sidebar-group.js +131 -0
- package/dist/cdn/seki-sidebar-header.d.ts +11 -0
- package/dist/cdn/seki-sidebar-header.js +38 -0
- package/dist/cdn/seki-sidebar-menu-item.d.ts +11 -0
- package/dist/cdn/seki-sidebar-menu-item.js +200 -0
- package/dist/cdn/seki-sidebar-menu-sub.d.ts +11 -0
- package/dist/cdn/seki-sidebar-menu-sub.js +123 -0
- package/dist/cdn/seki-sidebar-menu.d.ts +11 -0
- package/dist/cdn/seki-sidebar-menu.js +38 -0
- package/dist/cdn/seki-sidebar-trigger.d.ts +11 -0
- package/dist/cdn/seki-sidebar-trigger.js +109 -0
- package/dist/cdn/seki-sidebar.d.ts +11 -0
- package/dist/cdn/seki-sidebar.js +380 -0
- package/dist/cdn/seki-skeleton.js +2 -2
- package/dist/cdn/seki-switch.js +1 -1
- package/dist/cdn/seki-tooltip.js +1 -1
- package/dist/cjs/{index-Dd6_-KaR.js → index-tQYksITZ.js} +426 -6
- package/dist/cjs/index.cjs.js +1115 -63
- package/dist/cjs/keyboard-Cjl5HYES.js +142 -0
- package/dist/cjs/loader.cjs.js +2 -2
- package/dist/cjs/seki-button.cjs.entry.js +1 -1
- package/dist/cjs/seki-card-action.cjs.entry.js +1 -1
- package/dist/cjs/seki-card-content.cjs.entry.js +1 -1
- package/dist/cjs/seki-card-description.cjs.entry.js +1 -1
- package/dist/cjs/seki-card-footer.cjs.entry.js +1 -1
- package/dist/cjs/seki-card-header.cjs.entry.js +1 -1
- package/dist/cjs/seki-card-title.cjs.entry.js +1 -1
- package/dist/cjs/seki-card.cjs.entry.js +1 -1
- package/dist/cjs/seki-field-description.cjs.entry.js +1 -1
- package/dist/cjs/seki-field-error.cjs.entry.js +1 -1
- package/dist/cjs/seki-field-group.cjs.entry.js +1 -1
- package/dist/cjs/seki-field-label.cjs.entry.js +1 -1
- package/dist/cjs/seki-field-legend.cjs.entry.js +1 -1
- package/dist/cjs/seki-field.cjs.entry.js +1 -1
- package/dist/cjs/seki-fieldset.cjs.entry.js +1 -1
- package/dist/cjs/seki-input.cjs.entry.js +1 -1
- package/dist/cjs/seki-select-content.cjs.entry.js +1 -1
- package/dist/cjs/seki-select-group.cjs.entry.js +1 -1
- package/dist/cjs/seki-select-option.cjs.entry.js +1 -1
- package/dist/cjs/seki-select-trigger.cjs.entry.js +1 -1
- package/dist/cjs/seki-select.cjs.entry.js +1 -1
- package/dist/cjs/seki-sidebar-content.cjs.entry.js +20 -0
- package/dist/cjs/seki-sidebar-footer.cjs.entry.js +20 -0
- package/dist/cjs/seki-sidebar-group.cjs.entry.js +105 -0
- package/dist/cjs/seki-sidebar-header.cjs.entry.js +20 -0
- package/dist/cjs/seki-sidebar-menu-item.cjs.entry.js +174 -0
- package/dist/cjs/seki-sidebar-menu-sub.cjs.entry.js +99 -0
- package/dist/cjs/seki-sidebar-menu.cjs.entry.js +20 -0
- package/dist/cjs/seki-sidebar-trigger.cjs.entry.js +86 -0
- package/dist/cjs/seki-sidebar.cjs.entry.js +342 -0
- package/dist/cjs/seki-skeleton.cjs.entry.js +2 -2
- package/dist/cjs/seki-switch.cjs.entry.js +81 -3
- package/dist/cjs/seki-tooltip.cjs.entry.js +1 -1
- package/dist/cjs/sekiui.cjs.js +2 -2
- package/dist/collection/collection-manifest.json +9 -0
- package/dist/collection/components/sidebar/seki-sidebar-content.css +82 -0
- package/dist/collection/components/sidebar/seki-sidebar-content.js +33 -0
- package/dist/collection/components/sidebar/seki-sidebar-footer.css +44 -0
- package/dist/collection/components/sidebar/seki-sidebar-footer.js +31 -0
- package/dist/collection/components/sidebar/seki-sidebar-group.css +158 -0
- package/dist/collection/components/sidebar/seki-sidebar-group.js +300 -0
- package/dist/collection/components/sidebar/seki-sidebar-header.css +44 -0
- package/dist/collection/components/sidebar/seki-sidebar-header.js +32 -0
- package/dist/collection/components/sidebar/seki-sidebar-menu-item.css +196 -0
- package/dist/collection/components/sidebar/seki-sidebar-menu-item.js +403 -0
- package/dist/collection/components/sidebar/seki-sidebar-menu-sub.css +357 -0
- package/dist/collection/components/sidebar/seki-sidebar-menu-sub.js +256 -0
- package/dist/collection/components/sidebar/seki-sidebar-menu.css +25 -0
- package/dist/collection/components/sidebar/seki-sidebar-menu.js +32 -0
- package/dist/collection/components/sidebar/seki-sidebar-trigger.css +68 -0
- package/dist/collection/components/sidebar/seki-sidebar-trigger.js +175 -0
- package/dist/collection/components/sidebar/seki-sidebar.css +352 -0
- package/dist/collection/components/sidebar/seki-sidebar.js +812 -0
- package/dist/collection/components/sidebar/types.js +18 -0
- package/dist/collection/components/skeleton/seki-skeleton.js +1 -1
- package/dist/collection/index.js +7 -0
- package/dist/collection/services/focus.js +192 -0
- package/dist/collection/services/index.js +7 -0
- package/dist/collection/services/keyboard.js +136 -0
- package/dist/collection/services/media-query.js +254 -0
- package/dist/collection/types.js +41 -0
- package/dist/collection/utils/a11y.js +291 -0
- package/dist/collection/utils/common.js +286 -0
- package/dist/components/index.js +1053 -91
- package/dist/components/p-BJCq8m2o.js +138 -0
- package/dist/components/{p-QhPshhKB.js → p-BzYKy7d3.js} +427 -7
- package/dist/components/p-DwTISp-i.js +111 -0
- package/dist/components/seki-button.js +1 -1
- package/dist/components/seki-card-action.js +1 -1
- package/dist/components/seki-card-content.js +1 -1
- package/dist/components/seki-card-description.js +1 -1
- package/dist/components/seki-card-footer.js +1 -1
- package/dist/components/seki-card-header.js +1 -1
- package/dist/components/seki-card-title.js +1 -1
- package/dist/components/seki-card.js +1 -1
- package/dist/components/seki-field-description.js +1 -1
- package/dist/components/seki-field-error.js +1 -1
- package/dist/components/seki-field-group.js +1 -1
- package/dist/components/seki-field-label.js +1 -1
- package/dist/components/seki-field-legend.js +1 -1
- package/dist/components/seki-field.js +1 -1
- package/dist/components/seki-fieldset.js +1 -1
- package/dist/components/seki-input.js +1 -1
- package/dist/components/seki-select-content.js +1 -1
- package/dist/components/seki-select-group.js +1 -1
- package/dist/components/seki-select-option.js +1 -1
- package/dist/components/seki-select-trigger.js +1 -1
- package/dist/components/seki-select.js +1 -1
- package/dist/components/seki-sidebar-content.d.ts +11 -0
- package/dist/components/seki-sidebar-content.js +38 -0
- package/dist/components/seki-sidebar-footer.d.ts +11 -0
- package/dist/components/seki-sidebar-footer.js +38 -0
- package/dist/components/seki-sidebar-group.d.ts +11 -0
- package/dist/components/seki-sidebar-group.js +131 -0
- package/dist/components/seki-sidebar-header.d.ts +11 -0
- package/dist/components/seki-sidebar-header.js +38 -0
- package/dist/components/seki-sidebar-menu-item.d.ts +11 -0
- package/dist/components/seki-sidebar-menu-item.js +200 -0
- package/dist/components/seki-sidebar-menu-sub.d.ts +11 -0
- package/dist/components/seki-sidebar-menu-sub.js +123 -0
- package/dist/components/seki-sidebar-menu.d.ts +11 -0
- package/dist/components/seki-sidebar-menu.js +38 -0
- package/dist/components/seki-sidebar-trigger.d.ts +11 -0
- package/dist/components/seki-sidebar-trigger.js +109 -0
- package/dist/components/seki-sidebar.d.ts +11 -0
- package/dist/components/seki-sidebar.js +380 -0
- package/dist/components/seki-skeleton.js +2 -2
- package/dist/components/seki-switch.js +1 -1
- package/dist/components/seki-tooltip.js +1 -1
- package/dist/esm/{index-CuXbV_yz.js → index-Dfzpqq0a.js} +426 -6
- package/dist/esm/index.js +1053 -63
- package/dist/esm/keyboard-BJCq8m2o.js +138 -0
- package/dist/esm/loader.js +3 -3
- package/dist/esm/seki-button.entry.js +1 -1
- package/dist/esm/seki-card-action.entry.js +1 -1
- package/dist/esm/seki-card-content.entry.js +1 -1
- package/dist/esm/seki-card-description.entry.js +1 -1
- package/dist/esm/seki-card-footer.entry.js +1 -1
- package/dist/esm/seki-card-header.entry.js +1 -1
- package/dist/esm/seki-card-title.entry.js +1 -1
- package/dist/esm/seki-card.entry.js +1 -1
- package/dist/esm/seki-field-description.entry.js +1 -1
- package/dist/esm/seki-field-error.entry.js +1 -1
- package/dist/esm/seki-field-group.entry.js +1 -1
- package/dist/esm/seki-field-label.entry.js +1 -1
- package/dist/esm/seki-field-legend.entry.js +1 -1
- package/dist/esm/seki-field.entry.js +1 -1
- package/dist/esm/seki-fieldset.entry.js +1 -1
- package/dist/esm/seki-input.entry.js +1 -1
- package/dist/esm/seki-select-content.entry.js +1 -1
- package/dist/esm/seki-select-group.entry.js +1 -1
- package/dist/esm/seki-select-option.entry.js +1 -1
- package/dist/esm/seki-select-trigger.entry.js +1 -1
- package/dist/esm/seki-select.entry.js +1 -1
- package/dist/esm/seki-sidebar-content.entry.js +18 -0
- package/dist/esm/seki-sidebar-footer.entry.js +18 -0
- package/dist/esm/seki-sidebar-group.entry.js +103 -0
- package/dist/esm/seki-sidebar-header.entry.js +18 -0
- package/dist/esm/seki-sidebar-menu-item.entry.js +172 -0
- package/dist/esm/seki-sidebar-menu-sub.entry.js +97 -0
- package/dist/esm/seki-sidebar-menu.entry.js +18 -0
- package/dist/esm/seki-sidebar-trigger.entry.js +84 -0
- package/dist/esm/seki-sidebar.entry.js +340 -0
- package/dist/esm/seki-skeleton.entry.js +2 -2
- package/dist/esm/seki-switch.entry.js +84 -2
- package/dist/esm/seki-tooltip.entry.js +1 -1
- package/dist/esm/sekiui.js +3 -3
- package/dist/sekiui/index.esm.js +1 -1
- package/dist/sekiui/p-01cfb4e7.entry.js +1 -0
- package/dist/sekiui/{p-97e6e5ce.entry.js → p-042ec460.entry.js} +1 -1
- package/dist/sekiui/{p-3e088b5a.entry.js → p-10c008fc.entry.js} +1 -1
- package/dist/sekiui/{p-ed440425.entry.js → p-352bd295.entry.js} +1 -1
- package/dist/sekiui/p-37c5f4d9.entry.js +1 -0
- package/dist/sekiui/p-40fb71d6.entry.js +1 -0
- package/dist/sekiui/{p-eefbc037.entry.js → p-44191aed.entry.js} +1 -1
- package/dist/sekiui/{p-81709fc2.entry.js → p-4423d621.entry.js} +1 -1
- package/dist/sekiui/{p-9af5286b.entry.js → p-4636588f.entry.js} +1 -1
- package/dist/sekiui/p-471b97a5.entry.js +1 -0
- package/dist/sekiui/{p-0544d787.entry.js → p-56f0d754.entry.js} +1 -1
- package/dist/sekiui/{p-43f7c542.entry.js → p-5bc0f5aa.entry.js} +1 -1
- package/dist/sekiui/{p-eedf44b5.entry.js → p-6164cd8a.entry.js} +1 -1
- package/dist/sekiui/{p-b479935d.entry.js → p-635f4098.entry.js} +1 -1
- package/dist/sekiui/{p-35f8f9c4.entry.js → p-743fc6d9.entry.js} +1 -1
- package/dist/sekiui/p-83e65cbe.entry.js +1 -0
- package/dist/sekiui/p-8b7bd061.entry.js +1 -0
- package/dist/sekiui/{p-009183ab.entry.js → p-8d9a4878.entry.js} +1 -1
- package/dist/sekiui/p-9cb9cdfe.entry.js +1 -0
- package/dist/sekiui/p-9f2d95d7.entry.js +1 -0
- package/dist/sekiui/p-BJCq8m2o.js +1 -0
- package/dist/sekiui/p-Dfzpqq0a.js +2 -0
- package/dist/sekiui/{p-b22df79e.entry.js → p-a1a71958.entry.js} +1 -1
- package/dist/sekiui/{p-dd1e3e87.entry.js → p-a71e0c55.entry.js} +1 -1
- package/dist/sekiui/{p-1607dc4d.entry.js → p-ae227955.entry.js} +1 -1
- package/dist/sekiui/{p-cf11115c.entry.js → p-b365f5fb.entry.js} +1 -1
- package/dist/sekiui/{p-6bde807e.entry.js → p-b387a2a5.entry.js} +1 -1
- package/dist/sekiui/p-b8590f4d.entry.js +1 -0
- package/dist/sekiui/{p-88f91658.entry.js → p-c98b6d6a.entry.js} +1 -1
- package/dist/sekiui/{p-e71ad432.entry.js → p-d73cdb9a.entry.js} +1 -1
- package/dist/sekiui/{p-b10d81a6.entry.js → p-d96e770e.entry.js} +1 -1
- package/dist/sekiui/p-e62dd89b.entry.js +1 -0
- package/dist/sekiui/{p-d4c92041.entry.js → p-e7bb140c.entry.js} +1 -1
- package/dist/sekiui/{p-6e238adf.entry.js → p-eecc18f3.entry.js} +1 -1
- package/dist/sekiui/{p-4d57c6ea.entry.js → p-ff636955.entry.js} +1 -1
- package/dist/sekiui/sekiui.esm.js +1 -1
- package/dist/types/components/sidebar/seki-sidebar-content.d.ts +18 -0
- package/dist/types/components/sidebar/seki-sidebar-footer.d.ts +16 -0
- package/dist/types/components/sidebar/seki-sidebar-group.d.ts +81 -0
- package/dist/types/components/sidebar/seki-sidebar-header.d.ts +17 -0
- package/dist/types/components/sidebar/seki-sidebar-menu-item.d.ts +104 -0
- package/dist/types/components/sidebar/seki-sidebar-menu-sub.d.ts +81 -0
- package/dist/types/components/sidebar/seki-sidebar-menu.d.ts +17 -0
- package/dist/types/components/sidebar/seki-sidebar-trigger.d.ts +53 -0
- package/dist/types/components/sidebar/seki-sidebar.d.ts +185 -0
- package/dist/types/components/sidebar/types.d.ts +245 -0
- package/dist/types/components.d.ts +508 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/services/focus.d.ts +74 -0
- package/dist/types/services/index.d.ts +7 -0
- package/dist/types/services/keyboard.d.ts +74 -0
- package/dist/types/services/media-query.d.ts +121 -0
- package/dist/types/types.d.ts +105 -0
- package/dist/types/utils/a11y.d.ts +130 -0
- package/dist/types/utils/common.d.ts +142 -0
- package/package.json +2 -1
- package/dist/sekiui/p-9fe07f6e.entry.js +0 -1
- package/dist/sekiui/p-CuXbV_yz.js +0 -2
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar Component Type Definitions
|
|
3
|
+
* @module sidebar/types
|
|
4
|
+
*
|
|
5
|
+
* Complete TypeScript interfaces and types for the sidebar component system.
|
|
6
|
+
* These types are used throughout the component implementation, tests, and exported
|
|
7
|
+
* to the npm package for consumer IDE autocomplete support.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Default sidebar state values
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_SIDEBAR_STATE = {
|
|
13
|
+
isOpen: true,
|
|
14
|
+
collapseMode: 'offcanvas',
|
|
15
|
+
variant: 'sidebar',
|
|
16
|
+
isMobile: typeof window !== 'undefined' ? window.innerWidth < 768 : false,
|
|
17
|
+
persistenceKey: 'seki-sidebar-state',
|
|
18
|
+
};
|
|
@@ -15,7 +15,7 @@ export class SekiSkeleton {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
render() {
|
|
18
|
-
return (h("div", { key: '
|
|
18
|
+
return (h("div", { key: '4f60930ca73fef0cf4b2c940cbb108e05380caeb', class: "skeleton", part: "skeleton", role: "status", "aria-busy": "true", "aria-label": this.ariaLabel || undefined }));
|
|
19
19
|
}
|
|
20
20
|
static get is() { return "seki-skeleton"; }
|
|
21
21
|
static get encapsulation() { return "shadow"; }
|
package/dist/collection/index.js
CHANGED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Focus Management Service
|
|
3
|
+
* Provides utilities for focus management, focus traps, and focus restoration
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* FocusService - Centralized focus management
|
|
7
|
+
* Provides:
|
|
8
|
+
* - Focus trap implementation
|
|
9
|
+
* - Focus restoration
|
|
10
|
+
* - Focusable element detection
|
|
11
|
+
* - Focus utilities for accessibility
|
|
12
|
+
*/
|
|
13
|
+
export class FocusService {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.focusTrapStack = new Map();
|
|
16
|
+
this.previouslyFocusedElement = null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Get all focusable elements within a container
|
|
20
|
+
* Includes buttons, links, inputs, textareas, selects, and elements with tabindex
|
|
21
|
+
*/
|
|
22
|
+
getFocusableElements(container = document) {
|
|
23
|
+
const focusableSelectors = [
|
|
24
|
+
'button:not([disabled])',
|
|
25
|
+
'a[href]',
|
|
26
|
+
'input:not([disabled])',
|
|
27
|
+
'textarea:not([disabled])',
|
|
28
|
+
'select:not([disabled])',
|
|
29
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
30
|
+
].join(',');
|
|
31
|
+
const elements = Array.from(container.querySelectorAll(focusableSelectors));
|
|
32
|
+
return elements.filter((el) => {
|
|
33
|
+
// Exclude hidden elements
|
|
34
|
+
const style = window.getComputedStyle(el);
|
|
35
|
+
return style.display !== 'none' && style.visibility !== 'hidden';
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Check if an element is focusable
|
|
40
|
+
*/
|
|
41
|
+
isFocusable(element) {
|
|
42
|
+
const focusableSelectors = [
|
|
43
|
+
'button',
|
|
44
|
+
'a[href]',
|
|
45
|
+
'input',
|
|
46
|
+
'textarea',
|
|
47
|
+
'select',
|
|
48
|
+
'[tabindex]',
|
|
49
|
+
];
|
|
50
|
+
return focusableSelectors.some((selector) => element.matches(selector)) && !element.hasAttribute('disabled');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Set focus to an element with optional callbacks
|
|
54
|
+
*/
|
|
55
|
+
setFocus(element, options) {
|
|
56
|
+
var _a;
|
|
57
|
+
if (!element)
|
|
58
|
+
return false;
|
|
59
|
+
try {
|
|
60
|
+
element.focus({ preventScroll: (_a = options === null || options === void 0 ? void 0 : options.preventScroll) !== null && _a !== void 0 ? _a : false });
|
|
61
|
+
return document.activeElement === element;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.warn('Failed to set focus:', error);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Trap focus within a container (keyboard navigation stays within bounds)
|
|
70
|
+
* @param container - The element to trap focus within
|
|
71
|
+
* @param options - Focus trap options
|
|
72
|
+
*/
|
|
73
|
+
createFocusTrap(container, options = {}) {
|
|
74
|
+
if (typeof window === 'undefined')
|
|
75
|
+
return;
|
|
76
|
+
const { initialFocus, restoreFocus = true } = options;
|
|
77
|
+
// Store the previously focused element for restoration
|
|
78
|
+
if (restoreFocus) {
|
|
79
|
+
this.previouslyFocusedElement = document.activeElement;
|
|
80
|
+
}
|
|
81
|
+
// Set initial focus
|
|
82
|
+
if (initialFocus) {
|
|
83
|
+
this.setFocus(initialFocus);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const focusableElements = this.getFocusableElements(container);
|
|
87
|
+
if (focusableElements.length > 0) {
|
|
88
|
+
this.setFocus(focusableElements[0]);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Handle Tab key for focus trap
|
|
92
|
+
const handleKeyDown = (event) => {
|
|
93
|
+
if (event.key !== 'Tab')
|
|
94
|
+
return;
|
|
95
|
+
const focusableElements = this.getFocusableElements(container);
|
|
96
|
+
if (focusableElements.length === 0)
|
|
97
|
+
return;
|
|
98
|
+
const currentFocusIndex = focusableElements.indexOf(document.activeElement);
|
|
99
|
+
if (event.shiftKey) {
|
|
100
|
+
// Shift+Tab - move backwards
|
|
101
|
+
const previousIndex = currentFocusIndex - 1;
|
|
102
|
+
const targetElement = previousIndex >= 0 ? focusableElements[previousIndex] : focusableElements[focusableElements.length - 1];
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
this.setFocus(targetElement);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// Tab - move forwards
|
|
108
|
+
const nextIndex = currentFocusIndex + 1;
|
|
109
|
+
const targetElement = nextIndex < focusableElements.length ? focusableElements[nextIndex] : focusableElements[0];
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
this.setFocus(targetElement);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
container.addEventListener('keydown', handleKeyDown);
|
|
115
|
+
this.focusTrapStack.set(container, this.previouslyFocusedElement);
|
|
116
|
+
// Store the handler for potential later cleanup
|
|
117
|
+
// Note: Manual cleanup should be done via releaseFocusTrap()
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Release focus trap from a container
|
|
121
|
+
*/
|
|
122
|
+
releaseFocusTrap(container, restoreFocus = true) {
|
|
123
|
+
const previousElement = this.focusTrapStack.get(container);
|
|
124
|
+
this.focusTrapStack.delete(container);
|
|
125
|
+
if (restoreFocus && previousElement) {
|
|
126
|
+
this.setFocus(previousElement);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get the first focusable element in a container
|
|
131
|
+
*/
|
|
132
|
+
getFirstFocusable(container = document) {
|
|
133
|
+
const focusable = this.getFocusableElements(container);
|
|
134
|
+
return focusable.length > 0 ? focusable[0] : null;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get the last focusable element in a container
|
|
138
|
+
*/
|
|
139
|
+
getLastFocusable(container = document) {
|
|
140
|
+
const focusable = this.getFocusableElements(container);
|
|
141
|
+
return focusable.length > 0 ? focusable[focusable.length - 1] : null;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Move focus to the next focusable element
|
|
145
|
+
*/
|
|
146
|
+
focusNext(container = document) {
|
|
147
|
+
const focusable = this.getFocusableElements(container);
|
|
148
|
+
const currentIndex = focusable.indexOf(document.activeElement);
|
|
149
|
+
const nextIndex = (currentIndex + 1) % focusable.length;
|
|
150
|
+
if (focusable.length > 0) {
|
|
151
|
+
this.setFocus(focusable[nextIndex]);
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Move focus to the previous focusable element
|
|
158
|
+
*/
|
|
159
|
+
focusPrevious(container = document) {
|
|
160
|
+
const focusable = this.getFocusableElements(container);
|
|
161
|
+
const currentIndex = focusable.indexOf(document.activeElement);
|
|
162
|
+
const previousIndex = (currentIndex - 1 + focusable.length) % focusable.length;
|
|
163
|
+
if (focusable.length > 0) {
|
|
164
|
+
this.setFocus(focusable[previousIndex]);
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Clear all focus traps
|
|
171
|
+
*/
|
|
172
|
+
clearFocusTraps() {
|
|
173
|
+
this.focusTrapStack.clear();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Singleton instance
|
|
177
|
+
let focusServiceInstance = null;
|
|
178
|
+
/**
|
|
179
|
+
* Get the singleton FocusService instance
|
|
180
|
+
*/
|
|
181
|
+
export function getFocusService() {
|
|
182
|
+
if (!focusServiceInstance) {
|
|
183
|
+
focusServiceInstance = new FocusService();
|
|
184
|
+
}
|
|
185
|
+
return focusServiceInstance;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Create a new isolated FocusService instance
|
|
189
|
+
*/
|
|
190
|
+
export function createFocusService() {
|
|
191
|
+
return new FocusService();
|
|
192
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyboard Service
|
|
3
|
+
* Provides normalized keyboard event handling and shortcut management
|
|
4
|
+
* Handles cross-platform shortcuts (Ctrl/Cmd combinations)
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* KeyboardService - Centralized keyboard event management
|
|
8
|
+
* Provides:
|
|
9
|
+
* - Normalized keyboard shortcuts (handles Ctrl vs Cmd on Mac)
|
|
10
|
+
* - Cross-platform shortcut registration
|
|
11
|
+
* - Event cleanup and teardown
|
|
12
|
+
*/
|
|
13
|
+
export class KeyboardService {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.shortcuts = new Map();
|
|
16
|
+
this.listeners = new Map();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Register a keyboard shortcut
|
|
20
|
+
* @param id - Unique identifier for this shortcut
|
|
21
|
+
* @param options - Shortcut configuration
|
|
22
|
+
*/
|
|
23
|
+
registerShortcut(id, options) {
|
|
24
|
+
if (typeof window === 'undefined')
|
|
25
|
+
return;
|
|
26
|
+
const { key, ctrl = false, shift = false, alt = false, meta = false, handler, preventDefault = true } = options;
|
|
27
|
+
const handleKeyDown = (event) => {
|
|
28
|
+
const keyboardEvent = event;
|
|
29
|
+
const keyMatches = keyboardEvent.key.toLowerCase() === key.toLowerCase();
|
|
30
|
+
const ctrlMatches = ctrl ? keyboardEvent.ctrlKey : true;
|
|
31
|
+
const shiftMatches = shift ? keyboardEvent.shiftKey : !keyboardEvent.shiftKey;
|
|
32
|
+
const altMatches = alt ? keyboardEvent.altKey : !keyboardEvent.altKey;
|
|
33
|
+
// metaKey matches both metaKey (Mac Cmd) and ctrlKey (Windows/Linux) for cross-platform support
|
|
34
|
+
const metaMatches = meta ? keyboardEvent.metaKey || keyboardEvent.ctrlKey : !keyboardEvent.metaKey && !keyboardEvent.ctrlKey;
|
|
35
|
+
if (keyMatches && ctrlMatches && shiftMatches && altMatches && metaMatches) {
|
|
36
|
+
if (preventDefault) {
|
|
37
|
+
keyboardEvent.preventDefault();
|
|
38
|
+
}
|
|
39
|
+
handler(keyboardEvent);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
this.shortcuts.set(id, {
|
|
43
|
+
key,
|
|
44
|
+
ctrlKey: ctrl,
|
|
45
|
+
shiftKey: shift,
|
|
46
|
+
altKey: alt,
|
|
47
|
+
metaKey: meta,
|
|
48
|
+
handler: handleKeyDown,
|
|
49
|
+
});
|
|
50
|
+
this.listeners.set(id, handleKeyDown);
|
|
51
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Register a shortcut that works with Ctrl on Windows/Linux and Cmd on Mac
|
|
55
|
+
* @param id - Unique identifier for this shortcut
|
|
56
|
+
* @param key - The key to listen for
|
|
57
|
+
* @param handler - Callback when shortcut is triggered
|
|
58
|
+
* @param preventDefault - Whether to prevent default browser behavior
|
|
59
|
+
*/
|
|
60
|
+
registerCtrlOrCmdShortcut(id, key, handler, preventDefault = true) {
|
|
61
|
+
if (typeof window === 'undefined')
|
|
62
|
+
return;
|
|
63
|
+
const handleKeyDown = (event) => {
|
|
64
|
+
const keyboardEvent = event;
|
|
65
|
+
const keyMatches = keyboardEvent.key.toLowerCase() === key.toLowerCase();
|
|
66
|
+
const modifierMatches = keyboardEvent.ctrlKey || keyboardEvent.metaKey;
|
|
67
|
+
if (keyMatches && modifierMatches && !keyboardEvent.shiftKey && !keyboardEvent.altKey) {
|
|
68
|
+
if (preventDefault) {
|
|
69
|
+
keyboardEvent.preventDefault();
|
|
70
|
+
}
|
|
71
|
+
handler(keyboardEvent);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
this.listeners.set(id, handleKeyDown);
|
|
75
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Unregister a keyboard shortcut
|
|
79
|
+
* @param id - The identifier of the shortcut to remove
|
|
80
|
+
*/
|
|
81
|
+
unregisterShortcut(id) {
|
|
82
|
+
if (typeof window === 'undefined')
|
|
83
|
+
return;
|
|
84
|
+
const listener = this.listeners.get(id);
|
|
85
|
+
if (listener) {
|
|
86
|
+
window.removeEventListener('keydown', listener);
|
|
87
|
+
this.listeners.delete(id);
|
|
88
|
+
}
|
|
89
|
+
this.shortcuts.delete(id);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Unregister all shortcuts and clean up
|
|
93
|
+
*/
|
|
94
|
+
cleanup() {
|
|
95
|
+
if (typeof window === 'undefined')
|
|
96
|
+
return;
|
|
97
|
+
this.listeners.forEach((listener) => {
|
|
98
|
+
window.removeEventListener('keydown', listener);
|
|
99
|
+
});
|
|
100
|
+
this.listeners.clear();
|
|
101
|
+
this.shortcuts.clear();
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Check if a keyboard event matches a specific key combination
|
|
105
|
+
* @param event - The keyboard event to check
|
|
106
|
+
* @param key - The key to match
|
|
107
|
+
* @param ctrl - Whether Ctrl/Cmd should be pressed
|
|
108
|
+
* @param shift - Whether Shift should be pressed
|
|
109
|
+
* @param alt - Whether Alt should be pressed
|
|
110
|
+
*/
|
|
111
|
+
matchesShortcut(event, key, ctrl = false, shift = false, alt = false) {
|
|
112
|
+
const keyMatches = event.key.toLowerCase() === key.toLowerCase();
|
|
113
|
+
const ctrlMatches = ctrl ? event.ctrlKey || event.metaKey : !event.ctrlKey && !event.metaKey;
|
|
114
|
+
const shiftMatches = shift ? event.shiftKey : !event.shiftKey;
|
|
115
|
+
const altMatches = alt ? event.altKey : !event.altKey;
|
|
116
|
+
return keyMatches && ctrlMatches && shiftMatches && altMatches;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Singleton instance
|
|
120
|
+
let keyboardServiceInstance = null;
|
|
121
|
+
/**
|
|
122
|
+
* Get the singleton KeyboardService instance
|
|
123
|
+
*/
|
|
124
|
+
export function getKeyboardService() {
|
|
125
|
+
if (!keyboardServiceInstance) {
|
|
126
|
+
keyboardServiceInstance = new KeyboardService();
|
|
127
|
+
}
|
|
128
|
+
return keyboardServiceInstance;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Create a new isolated KeyboardService instance
|
|
132
|
+
* Useful for testing or when you need independent event handling
|
|
133
|
+
*/
|
|
134
|
+
export function createKeyboardService() {
|
|
135
|
+
return new KeyboardService();
|
|
136
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Query Service
|
|
3
|
+
* Provides utilities for responsive design and media query management
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Default breakpoints (Tailwind-inspired)
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_BREAKPOINTS = {
|
|
9
|
+
sm: '640px',
|
|
10
|
+
md: '768px',
|
|
11
|
+
lg: '1024px',
|
|
12
|
+
xl: '1280px',
|
|
13
|
+
'2xl': '1536px',
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* MediaQueryService - Centralized media query management
|
|
17
|
+
* Provides:
|
|
18
|
+
* - Media query listener registration
|
|
19
|
+
* - Breakpoint detection
|
|
20
|
+
* - Window resize handling
|
|
21
|
+
* - Current viewport information
|
|
22
|
+
*/
|
|
23
|
+
export class MediaQueryService {
|
|
24
|
+
constructor(breakpoints = DEFAULT_BREAKPOINTS) {
|
|
25
|
+
this.listeners = new Map();
|
|
26
|
+
this.resizeObserver = null;
|
|
27
|
+
this.resizeListeners = new Map();
|
|
28
|
+
this.breakpoints = breakpoints;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Register a media query listener
|
|
32
|
+
* @param id - Unique identifier for this listener
|
|
33
|
+
* @param query - CSS media query string
|
|
34
|
+
* @param handler - Callback when media query matches/unmatches
|
|
35
|
+
*/
|
|
36
|
+
registerMediaQuery(id, query, handler) {
|
|
37
|
+
if (typeof window === 'undefined')
|
|
38
|
+
return;
|
|
39
|
+
try {
|
|
40
|
+
const matcher = window.matchMedia(query);
|
|
41
|
+
const listener = { query, handler, matcher };
|
|
42
|
+
// Call handler with initial state
|
|
43
|
+
handler(matcher.matches);
|
|
44
|
+
// Listen for changes
|
|
45
|
+
matcher.addEventListener('change', (e) => handler(e.matches));
|
|
46
|
+
this.listeners.set(id, listener);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.warn(`Failed to register media query "${query}":`, error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Register a breakpoint listener (easier than raw media queries)
|
|
54
|
+
* @param id - Unique identifier
|
|
55
|
+
* @param breakpoint - Breakpoint name (sm, md, lg, xl, 2xl)
|
|
56
|
+
* @param type - Type of match: 'min' (mobile-first) or 'max' (desktop-first)
|
|
57
|
+
* @param handler - Callback when breakpoint matches
|
|
58
|
+
*/
|
|
59
|
+
registerBreakpoint(id, breakpoint, type = 'min', handler) {
|
|
60
|
+
const size = this.breakpoints[breakpoint];
|
|
61
|
+
if (!size) {
|
|
62
|
+
console.warn(`Unknown breakpoint: ${breakpoint}`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const query = type === 'min' ? `(min-width: ${size})` : `(max-width: ${size})`;
|
|
66
|
+
this.registerMediaQuery(id, query, handler);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Unregister a media query listener
|
|
70
|
+
*/
|
|
71
|
+
unregisterMediaQuery(id) {
|
|
72
|
+
const listener = this.listeners.get(id);
|
|
73
|
+
if (listener && listener.matcher) {
|
|
74
|
+
listener.matcher.removeEventListener('change', () => { });
|
|
75
|
+
}
|
|
76
|
+
this.listeners.delete(id);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if a media query currently matches
|
|
80
|
+
*/
|
|
81
|
+
isMatching(query) {
|
|
82
|
+
if (typeof window === 'undefined')
|
|
83
|
+
return false;
|
|
84
|
+
try {
|
|
85
|
+
return window.matchMedia(query).matches;
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.warn(`Failed to check media query "${query}":`, error);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Check if a breakpoint is currently active
|
|
94
|
+
*/
|
|
95
|
+
isBreakpointActive(breakpoint, type = 'min') {
|
|
96
|
+
const size = this.breakpoints[breakpoint];
|
|
97
|
+
if (!size)
|
|
98
|
+
return false;
|
|
99
|
+
const query = type === 'min' ? `(min-width: ${size})` : `(max-width: ${size})`;
|
|
100
|
+
return this.isMatching(query);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get the current viewport width
|
|
104
|
+
*/
|
|
105
|
+
getViewportWidth() {
|
|
106
|
+
if (typeof window === 'undefined')
|
|
107
|
+
return 0;
|
|
108
|
+
return window.innerWidth;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get the current viewport height
|
|
112
|
+
*/
|
|
113
|
+
getViewportHeight() {
|
|
114
|
+
if (typeof window === 'undefined')
|
|
115
|
+
return 0;
|
|
116
|
+
return window.innerHeight;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Check if viewport is mobile-sized (< 768px)
|
|
120
|
+
*/
|
|
121
|
+
isMobile() {
|
|
122
|
+
return this.getViewportWidth() < 768;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Check if viewport is tablet-sized (768px - 1024px)
|
|
126
|
+
*/
|
|
127
|
+
isTablet() {
|
|
128
|
+
const width = this.getViewportWidth();
|
|
129
|
+
return width >= 768 && width < 1024;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Check if viewport is desktop-sized (>= 1024px)
|
|
133
|
+
*/
|
|
134
|
+
isDesktop() {
|
|
135
|
+
return this.getViewportWidth() >= 1024;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Check if prefers-reduced-motion is enabled
|
|
139
|
+
*/
|
|
140
|
+
prefersReducedMotion() {
|
|
141
|
+
if (typeof window === 'undefined')
|
|
142
|
+
return false;
|
|
143
|
+
try {
|
|
144
|
+
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
145
|
+
}
|
|
146
|
+
catch (_a) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Check if dark mode is preferred
|
|
152
|
+
*/
|
|
153
|
+
prefersDarkMode() {
|
|
154
|
+
if (typeof window === 'undefined')
|
|
155
|
+
return false;
|
|
156
|
+
try {
|
|
157
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
158
|
+
}
|
|
159
|
+
catch (_a) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get the current device orientation
|
|
165
|
+
*/
|
|
166
|
+
getOrientation() {
|
|
167
|
+
if (typeof window === 'undefined')
|
|
168
|
+
return 'portrait';
|
|
169
|
+
return window.innerHeight > window.innerWidth ? 'portrait' : 'landscape';
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Register a window resize listener
|
|
173
|
+
* @param id - Unique identifier
|
|
174
|
+
* @param handler - Callback with (width, height)
|
|
175
|
+
* @param debounceMs - Debounce delay in milliseconds
|
|
176
|
+
*/
|
|
177
|
+
registerResizeListener(id, handler, debounceMs = 0) {
|
|
178
|
+
if (typeof window === 'undefined')
|
|
179
|
+
return;
|
|
180
|
+
let timeoutId = null;
|
|
181
|
+
const eventListener = () => {
|
|
182
|
+
if (timeoutId)
|
|
183
|
+
clearTimeout(timeoutId);
|
|
184
|
+
if (debounceMs > 0) {
|
|
185
|
+
timeoutId = setTimeout(() => {
|
|
186
|
+
handler(this.getViewportWidth(), this.getViewportHeight());
|
|
187
|
+
}, debounceMs);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
handler(this.getViewportWidth(), this.getViewportHeight());
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
this.resizeListeners.set(id, { handler, eventListener });
|
|
194
|
+
window.addEventListener('resize', eventListener);
|
|
195
|
+
// Call handler with initial dimensions
|
|
196
|
+
handler(this.getViewportWidth(), this.getViewportHeight());
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Unregister a resize listener
|
|
200
|
+
*/
|
|
201
|
+
unregisterResizeListener(id) {
|
|
202
|
+
if (typeof window === 'undefined')
|
|
203
|
+
return;
|
|
204
|
+
const entry = this.resizeListeners.get(id);
|
|
205
|
+
if (entry) {
|
|
206
|
+
window.removeEventListener('resize', entry.eventListener);
|
|
207
|
+
this.resizeListeners.delete(id);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Clean up all listeners
|
|
212
|
+
*/
|
|
213
|
+
cleanup() {
|
|
214
|
+
this.listeners.forEach((listener) => {
|
|
215
|
+
if (listener.matcher) {
|
|
216
|
+
listener.matcher.removeEventListener('change', () => { });
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
this.listeners.clear();
|
|
220
|
+
if (typeof window !== 'undefined') {
|
|
221
|
+
this.resizeListeners.forEach((entry) => {
|
|
222
|
+
window.removeEventListener('resize', entry.eventListener);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
this.resizeListeners.clear();
|
|
226
|
+
if (this.resizeObserver) {
|
|
227
|
+
this.resizeObserver.disconnect();
|
|
228
|
+
this.resizeObserver = null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Update breakpoint configuration
|
|
233
|
+
*/
|
|
234
|
+
setBreakpoints(breakpoints) {
|
|
235
|
+
this.breakpoints = Object.assign(Object.assign({}, DEFAULT_BREAKPOINTS), breakpoints);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Singleton instance
|
|
239
|
+
let mediaQueryServiceInstance = null;
|
|
240
|
+
/**
|
|
241
|
+
* Get the singleton MediaQueryService instance
|
|
242
|
+
*/
|
|
243
|
+
export function getMediaQueryService(breakpoints) {
|
|
244
|
+
if (!mediaQueryServiceInstance) {
|
|
245
|
+
mediaQueryServiceInstance = new MediaQueryService(breakpoints);
|
|
246
|
+
}
|
|
247
|
+
return mediaQueryServiceInstance;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Create a new isolated MediaQueryService instance
|
|
251
|
+
*/
|
|
252
|
+
export function createMediaQueryService(breakpoints) {
|
|
253
|
+
return new MediaQueryService(breakpoints || DEFAULT_BREAKPOINTS);
|
|
254
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SekiUI Type Definitions
|
|
3
|
+
* Central export point for all type definitions across components and services
|
|
4
|
+
*/
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// NOTE: Types from ./components/sidebar/types and ./utils/select/types
|
|
7
|
+
// are already re-exported at the top of this file via the main index.ts
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// TYPE GUARDS AND UTILITY TYPES
|
|
11
|
+
// ============================================================================
|
|
12
|
+
/**
|
|
13
|
+
* Type guard to check if a value is a valid sidebar variant
|
|
14
|
+
*/
|
|
15
|
+
export function isSidebarVariant(value) {
|
|
16
|
+
return ['sidebar', 'floating', 'inset'].includes(value);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Type guard to check if a value is a valid collapse mode
|
|
20
|
+
*/
|
|
21
|
+
export function isCollapseMode(value) {
|
|
22
|
+
return ['offcanvas', 'icon', 'none'].includes(value);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Type guard to check if a value is a valid button size
|
|
26
|
+
*/
|
|
27
|
+
export function isButtonSize(value) {
|
|
28
|
+
return ['sm', 'md', 'lg', 'icon-sm', 'icon', 'icon-lg'].includes(value);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Type guard to check if a value is a valid button variant
|
|
32
|
+
*/
|
|
33
|
+
export function isButtonVariant(value) {
|
|
34
|
+
return ['primary', 'secondary', 'outline', 'ghost', 'destructive', 'link'].includes(value);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Type guard to check if a value is a valid tooltip side
|
|
38
|
+
*/
|
|
39
|
+
export function isTooltipSide(value) {
|
|
40
|
+
return ['top', 'right', 'bottom', 'left'].includes(value);
|
|
41
|
+
}
|