@human-kit/svelte-components 1.0.0-alpha.1 → 1.0.0-alpha.3
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/FOCUS_STATE_CONTRACT.md +51 -0
- package/dist/FOCUS_STATE_REVIEW_TEMPLATE.md +70 -0
- package/dist/calendar/README.md +66 -0
- package/dist/calendar/TODO.md +23 -0
- package/dist/calendar/body-cell/calendar-body-cell.svelte +230 -0
- package/dist/calendar/body-cell/calendar-body-cell.svelte.d.ts +9 -0
- package/dist/calendar/grid/calendar-grid-month-scope.svelte +16 -0
- package/dist/calendar/grid/calendar-grid-month-scope.svelte.d.ts +8 -0
- package/dist/calendar/grid/calendar-grid.svelte +45 -0
- package/dist/calendar/grid/calendar-grid.svelte.d.ts +8 -0
- package/dist/calendar/grid/month-scope.d.ts +2 -0
- package/dist/calendar/grid/month-scope.js +8 -0
- package/dist/calendar/grid-body/calendar-grid-body-custom-test.svelte +13 -0
- package/dist/calendar/grid-body/calendar-grid-body-custom-test.svelte.d.ts +18 -0
- package/dist/calendar/grid-body/calendar-grid-body.svelte +36 -0
- package/dist/calendar/grid-body/calendar-grid-body.svelte.d.ts +8 -0
- package/dist/calendar/grid-header/calendar-grid-header-custom-test.svelte +13 -0
- package/dist/calendar/grid-header/calendar-grid-header-custom-test.svelte.d.ts +18 -0
- package/dist/calendar/grid-header/calendar-grid-header.svelte +31 -0
- package/dist/calendar/grid-header/calendar-grid-header.svelte.d.ts +8 -0
- package/dist/calendar/header-cell/calendar-header-cell-test.svelte +11 -0
- package/dist/calendar/header-cell/calendar-header-cell-test.svelte.d.ts +18 -0
- package/dist/calendar/header-cell/calendar-header-cell.svelte +16 -0
- package/dist/calendar/header-cell/calendar-header-cell.svelte.d.ts +8 -0
- package/dist/calendar/heading/calendar-heading.svelte +17 -0
- package/dist/calendar/heading/calendar-heading.svelte.d.ts +5 -0
- package/dist/calendar/index.d.ts +13 -0
- package/dist/calendar/index.js +13 -0
- package/dist/calendar/index.parts.d.ts +9 -0
- package/dist/calendar/index.parts.js +9 -0
- package/dist/calendar/root/calendar-root-bind-value-test.svelte +14 -0
- package/dist/calendar/root/calendar-root-bind-value-test.svelte.d.ts +3 -0
- package/dist/calendar/root/calendar-root-controlled-clear-test.svelte +20 -0
- package/dist/calendar/root/calendar-root-controlled-clear-test.svelte.d.ts +3 -0
- package/dist/calendar/root/calendar-root-test.svelte +71 -0
- package/dist/calendar/root/calendar-root-test.svelte.d.ts +13 -0
- package/dist/calendar/root/calendar-root.svelte +143 -0
- package/dist/calendar/root/calendar-root.svelte.d.ts +31 -0
- package/dist/calendar/root/context.d.ts +66 -0
- package/dist/calendar/root/context.js +727 -0
- package/dist/calendar/root/date-utils.d.ts +17 -0
- package/dist/calendar/root/date-utils.js +94 -0
- package/dist/calendar/trigger-next/calendar-trigger-next.svelte +38 -0
- package/dist/calendar/trigger-next/calendar-trigger-next.svelte.d.ts +8 -0
- package/dist/calendar/trigger-previous/calendar-trigger-previous.svelte +38 -0
- package/dist/calendar/trigger-previous/calendar-trigger-previous.svelte.d.ts +8 -0
- package/dist/combobox/README.md +40 -0
- package/dist/combobox/TODO.md +28 -175
- package/dist/combobox/button/README.md +15 -0
- package/dist/combobox/button/combobox-button.svelte +2 -0
- package/dist/combobox/input/README.md +16 -0
- package/dist/combobox/item/README.md +27 -0
- package/dist/combobox/item-indicator/README.md +15 -0
- package/dist/combobox/list/README.md +27 -0
- package/dist/combobox/popover/README.md +13 -0
- package/dist/combobox/root/README.md +44 -0
- package/dist/combobox/root/combobox.svelte +30 -0
- package/dist/combobox/tag/README.md +37 -0
- package/dist/combobox/tag-remove/README.md +14 -0
- package/dist/combobox/tags/README.md +23 -0
- package/dist/datepicker/README.md +100 -0
- package/dist/datepicker/TODO.md +28 -0
- package/dist/datepicker/calendar/date-picker-calendar-unsafe-props-test.svelte +60 -0
- package/dist/datepicker/calendar/date-picker-calendar-unsafe-props-test.svelte.d.ts +3 -0
- package/dist/datepicker/calendar/date-picker-calendar.svelte +65 -0
- package/dist/datepicker/calendar/date-picker-calendar.svelte.d.ts +10 -0
- package/dist/datepicker/index.d.ts +18 -0
- package/dist/datepicker/index.js +18 -0
- package/dist/datepicker/index.parts.d.ts +14 -0
- package/dist/datepicker/index.parts.js +14 -0
- package/dist/datepicker/input/date-picker-input.svelte +108 -0
- package/dist/datepicker/input/date-picker-input.svelte.d.ts +11 -0
- package/dist/datepicker/internal/strict-props.d.ts +2 -0
- package/dist/datepicker/internal/strict-props.js +28 -0
- package/dist/datepicker/popover/date-picker-popover-handler-test.svelte +57 -0
- package/dist/datepicker/popover/date-picker-popover-handler-test.svelte.d.ts +3 -0
- package/dist/datepicker/popover/date-picker-popover-unsafe-props-test.svelte +45 -0
- package/dist/datepicker/popover/date-picker-popover-unsafe-props-test.svelte.d.ts +18 -0
- package/dist/datepicker/popover/date-picker-popover.svelte +87 -0
- package/dist/datepicker/popover/date-picker-popover.svelte.d.ts +7 -0
- package/dist/datepicker/root/context.d.ts +43 -0
- package/dist/datepicker/root/context.js +15 -0
- package/dist/datepicker/root/date-picker-bindable-empty-test.svelte +24 -0
- package/dist/datepicker/root/date-picker-bindable-empty-test.svelte.d.ts +3 -0
- package/dist/datepicker/root/date-picker-bindable-test.svelte +41 -0
- package/dist/datepicker/root/date-picker-bindable-test.svelte.d.ts +3 -0
- package/dist/datepicker/root/date-picker-empty-test.svelte +47 -0
- package/dist/datepicker/root/date-picker-empty-test.svelte.d.ts +3 -0
- package/dist/datepicker/root/date-picker-locale-typing-test.svelte +47 -0
- package/dist/datepicker/root/date-picker-locale-typing-test.svelte.d.ts +3 -0
- package/dist/datepicker/root/date-picker-open-cancel-test.svelte +54 -0
- package/dist/datepicker/root/date-picker-open-cancel-test.svelte.d.ts +8 -0
- package/dist/datepicker/root/date-picker-root.svelte +495 -0
- package/dist/datepicker/root/date-picker-root.svelte.d.ts +24 -0
- package/dist/datepicker/root/date-picker-test.svelte +86 -0
- package/dist/datepicker/root/date-picker-test.svelte.d.ts +13 -0
- package/dist/datepicker/root/date-utils.d.ts +17 -0
- package/dist/datepicker/root/date-utils.js +138 -0
- package/dist/datepicker/root/draft-evaluation.d.ts +13 -0
- package/dist/datepicker/root/draft-evaluation.js +56 -0
- package/dist/datepicker/root/focus-controller.d.ts +3 -0
- package/dist/datepicker/root/focus-controller.js +15 -0
- package/dist/datepicker/root/open-change.d.ts +5 -0
- package/dist/datepicker/root/open-change.js +13 -0
- package/dist/datepicker/root/open-controller.d.ts +7 -0
- package/dist/datepicker/root/open-controller.js +15 -0
- package/dist/datepicker/root/segment-controller.d.ts +8 -0
- package/dist/datepicker/root/segment-controller.js +53 -0
- package/dist/datepicker/root/segment-state.d.ts +18 -0
- package/dist/datepicker/root/segment-state.js +134 -0
- package/dist/datepicker/root/value-commit.d.ts +4 -0
- package/dist/datepicker/root/value-commit.js +8 -0
- package/dist/datepicker/segment/date-picker-segment.svelte +319 -0
- package/dist/datepicker/segment/date-picker-segment.svelte.d.ts +9 -0
- package/dist/datepicker/trigger/date-picker-trigger.svelte +110 -0
- package/dist/datepicker/trigger/date-picker-trigger.svelte.d.ts +9 -0
- package/dist/dialog/README.md +35 -0
- package/dist/dialog/content/README.md +16 -0
- package/dist/dialog/content/dialog-content.svelte +6 -6
- package/dist/dialog/overlay/README.md +13 -0
- package/dist/dialog/portal/README.md +12 -0
- package/dist/dialog/root/README.md +53 -0
- package/dist/dialog/root/context.d.ts +2 -1
- package/dist/dialog/root/dialog-root.svelte +9 -2
- package/dist/dialog/trigger/README.md +12 -0
- package/dist/dialog/trigger/dialog-trigger-multi-button-test.svelte +19 -0
- package/dist/dialog/trigger/dialog-trigger-multi-button-test.svelte.d.ts +18 -0
- package/dist/dialog/trigger/dialog-trigger.svelte +18 -6
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/listbox/README.md +26 -0
- package/dist/listbox/item/README.md +24 -0
- package/dist/listbox/root/README.md +40 -0
- package/dist/listbox/root/listbox.svelte +44 -0
- package/dist/locale-provider/context.d.ts +8 -0
- package/dist/locale-provider/context.js +18 -0
- package/dist/locale-provider/index.d.ts +4 -0
- package/dist/locale-provider/index.js +4 -0
- package/dist/locale-provider/locale-provider-initial-value-test.svelte +15 -0
- package/dist/locale-provider/locale-provider-initial-value-test.svelte.d.ts +7 -0
- package/dist/locale-provider/locale-provider-test.svelte +20 -0
- package/dist/locale-provider/locale-provider-test.svelte.d.ts +6 -0
- package/dist/locale-provider/locale-provider-value-probe.svelte +22 -0
- package/dist/locale-provider/locale-provider-value-probe.svelte.d.ts +6 -0
- package/dist/locale-provider/locale-provider.svelte +23 -0
- package/dist/locale-provider/locale-provider.svelte.d.ts +8 -0
- package/dist/popover/README.md +42 -0
- package/dist/popover/content/README.md +25 -0
- package/dist/popover/content/popover-content-standalone-test.svelte +28 -0
- package/dist/popover/content/popover-content-standalone-test.svelte.d.ts +6 -0
- package/dist/popover/content/popover-content-test.svelte +2 -1
- package/dist/popover/content/popover-content-test.svelte.d.ts +2 -1
- package/dist/popover/content/popover-content.svelte +91 -18
- package/dist/popover/content/popover-content.svelte.d.ts +5 -1
- package/dist/popover/index.d.ts +1 -1
- package/dist/popover/index.js +1 -3
- package/dist/popover/root/README.md +25 -0
- package/dist/popover/root/context.d.ts +16 -7
- package/dist/popover/root/context.js +0 -2
- package/dist/popover/root/focus-state.d.ts +4 -0
- package/dist/popover/root/focus-state.js +33 -0
- package/dist/popover/root/popover-root.svelte +90 -17
- package/dist/popover/root/popover-root.svelte.d.ts +2 -1
- package/dist/popover/root/popover-test.svelte +2 -1
- package/dist/popover/root/popover-test.svelte.d.ts +2 -1
- package/dist/popover/trigger/README.md +23 -0
- package/dist/popover/trigger/popover-trigger-button.svelte +10 -7
- package/dist/popover/trigger/popover-trigger-button.svelte.d.ts +2 -3
- package/dist/popover/trigger/popover-trigger-multi-button-test.svelte +16 -0
- package/dist/popover/trigger/popover-trigger-multi-button-test.svelte.d.ts +18 -0
- package/dist/popover/trigger/popover-trigger.svelte +19 -7
- package/dist/portal/portal.svelte +3 -1
- package/dist/primitives/click-outside.d.ts +1 -1
- package/dist/primitives/click-outside.js +1 -1
- package/dist/primitives/focus-trap.d.ts +7 -2
- package/dist/primitives/focus-trap.js +40 -6
- package/dist/primitives/index.d.ts +1 -0
- package/dist/primitives/index.js +1 -0
- package/dist/primitives/input-modality.d.ts +7 -0
- package/dist/primitives/input-modality.js +116 -0
- package/dist/test-utils/focus-contract.d.ts +3 -0
- package/dist/test-utils/focus-contract.js +26 -0
- package/dist/utils/date-only.d.ts +11 -0
- package/dist/utils/date-only.js +53 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/package.json +16 -1
|
@@ -24,17 +24,26 @@
|
|
|
24
24
|
const popoverCtx = ctx;
|
|
25
25
|
|
|
26
26
|
let wrapperRef: HTMLElement | null = $state(null);
|
|
27
|
+
let activeTrigger: HTMLElement | null = null;
|
|
28
|
+
|
|
29
|
+
function setActiveTrigger(button: HTMLElement) {
|
|
30
|
+
if (activeTrigger && activeTrigger !== button) {
|
|
31
|
+
activeTrigger.setAttribute('aria-expanded', 'false');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
activeTrigger = button;
|
|
35
|
+
popoverCtx.setTriggerRef(button);
|
|
36
|
+
button.setAttribute('aria-haspopup', 'dialog');
|
|
37
|
+
button.setAttribute('aria-expanded', String(popoverCtx.isOpen));
|
|
38
|
+
}
|
|
27
39
|
|
|
28
40
|
function handleClick(event: MouseEvent) {
|
|
29
41
|
const target = event.target as HTMLElement;
|
|
30
42
|
const button = target.closest('button, [role="button"]') as HTMLElement | null;
|
|
31
43
|
|
|
32
44
|
if (button && wrapperRef?.contains(button)) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
popoverCtx.setTriggerRef(button);
|
|
36
|
-
}
|
|
37
|
-
popoverCtx.toggle();
|
|
45
|
+
setActiveTrigger(button);
|
|
46
|
+
popoverCtx.toggle('trigger-press', event);
|
|
38
47
|
}
|
|
39
48
|
}
|
|
40
49
|
|
|
@@ -43,8 +52,7 @@
|
|
|
43
52
|
// Find and set up the trigger button
|
|
44
53
|
const firstButton = wrapperRef.querySelector('button, [role="button"]') as HTMLElement | null;
|
|
45
54
|
if (firstButton) {
|
|
46
|
-
|
|
47
|
-
firstButton.setAttribute('aria-haspopup', 'dialog');
|
|
55
|
+
setActiveTrigger(firstButton);
|
|
48
56
|
}
|
|
49
57
|
}
|
|
50
58
|
|
|
@@ -59,6 +67,10 @@
|
|
|
59
67
|
|
|
60
68
|
$effect(() => {
|
|
61
69
|
if (popoverCtx.triggerRef) {
|
|
70
|
+
if (activeTrigger !== popoverCtx.triggerRef) {
|
|
71
|
+
activeTrigger = popoverCtx.triggerRef;
|
|
72
|
+
}
|
|
73
|
+
popoverCtx.triggerRef.setAttribute('aria-haspopup', 'dialog');
|
|
62
74
|
popoverCtx.triggerRef.setAttribute('aria-expanded', String(popoverCtx.isOpen));
|
|
63
75
|
}
|
|
64
76
|
});
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
if (!browser || !wrapper) return;
|
|
19
19
|
|
|
20
20
|
await tick();
|
|
21
|
+
const wrapperNode = wrapper;
|
|
22
|
+
if (!wrapperNode) return;
|
|
21
23
|
|
|
22
24
|
const targetEl = document.querySelector(target);
|
|
23
25
|
if (!targetEl) {
|
|
@@ -25,7 +27,7 @@
|
|
|
25
27
|
return;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
targetEl.appendChild(
|
|
30
|
+
targetEl.appendChild(wrapperNode);
|
|
29
31
|
});
|
|
30
32
|
|
|
31
33
|
onDestroy(() => {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export type ClickOutsideOptions = {
|
|
6
6
|
/** Callback when clicking outside. */
|
|
7
|
-
handler: () => void;
|
|
7
|
+
handler: (event: MouseEvent) => void;
|
|
8
8
|
/** Whether the listener is enabled. */
|
|
9
9
|
enabled?: boolean;
|
|
10
10
|
/** Elements to ignore (clicks on these won't trigger). */
|
|
@@ -41,7 +41,7 @@ export function clickOutside(node, options) {
|
|
|
41
41
|
// This prevents closing when clicking on nested popovers/dialogs
|
|
42
42
|
if (isInTopLayer(target))
|
|
43
43
|
return;
|
|
44
|
-
handler();
|
|
44
|
+
handler(event);
|
|
45
45
|
}
|
|
46
46
|
if (enabled) {
|
|
47
47
|
document.addEventListener('mousedown', handleClick, true);
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
* Focus trap primitive.
|
|
3
3
|
* Traps keyboard focus within a container element.
|
|
4
4
|
*/
|
|
5
|
+
export type FocusTrapOptions = {
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
restoreFocus?: boolean;
|
|
8
|
+
initialFocus?: HTMLElement | string | (() => HTMLElement | null | undefined);
|
|
9
|
+
};
|
|
5
10
|
/**
|
|
6
11
|
* Svelte action that traps focus within an element.
|
|
7
12
|
*
|
|
@@ -13,7 +18,7 @@
|
|
|
13
18
|
* </div>
|
|
14
19
|
* ```
|
|
15
20
|
*/
|
|
16
|
-
export declare function focusTrap(node: HTMLElement,
|
|
17
|
-
update(
|
|
21
|
+
export declare function focusTrap(node: HTMLElement, options?: boolean | FocusTrapOptions): {
|
|
22
|
+
update(newOptions: boolean | FocusTrapOptions): void;
|
|
18
23
|
destroy(): void;
|
|
19
24
|
};
|
|
@@ -11,6 +11,27 @@ const FOCUSABLE_SELECTOR = [
|
|
|
11
11
|
'[tabindex]:not([tabindex="-1"])',
|
|
12
12
|
'[contenteditable="true"]'
|
|
13
13
|
].join(', ');
|
|
14
|
+
function resolveEnabled(options) {
|
|
15
|
+
if (typeof options === 'boolean')
|
|
16
|
+
return options;
|
|
17
|
+
return options.enabled ?? true;
|
|
18
|
+
}
|
|
19
|
+
function resolveRestoreFocus(options) {
|
|
20
|
+
if (typeof options === 'boolean')
|
|
21
|
+
return true;
|
|
22
|
+
return options.restoreFocus ?? true;
|
|
23
|
+
}
|
|
24
|
+
function resolveInitialFocus(container, initialFocus) {
|
|
25
|
+
if (!initialFocus)
|
|
26
|
+
return null;
|
|
27
|
+
if (typeof initialFocus === 'function') {
|
|
28
|
+
return initialFocus() ?? null;
|
|
29
|
+
}
|
|
30
|
+
if (typeof initialFocus === 'string') {
|
|
31
|
+
return container.querySelector(initialFocus);
|
|
32
|
+
}
|
|
33
|
+
return initialFocus;
|
|
34
|
+
}
|
|
14
35
|
function getFocusableElements(container) {
|
|
15
36
|
return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => el.offsetParent !== null);
|
|
16
37
|
}
|
|
@@ -25,8 +46,11 @@ function getFocusableElements(container) {
|
|
|
25
46
|
* </div>
|
|
26
47
|
* ```
|
|
27
48
|
*/
|
|
28
|
-
export function focusTrap(node,
|
|
49
|
+
export function focusTrap(node, options = true) {
|
|
29
50
|
let previousActiveElement = null;
|
|
51
|
+
let enabled = resolveEnabled(options);
|
|
52
|
+
let restoreFocus = resolveRestoreFocus(options);
|
|
53
|
+
let initialFocus = typeof options === 'boolean' ? undefined : options.initialFocus;
|
|
30
54
|
function handleKeydown(event) {
|
|
31
55
|
if (event.key !== 'Tab')
|
|
32
56
|
return;
|
|
@@ -64,6 +88,11 @@ export function focusTrap(node, enabled = true) {
|
|
|
64
88
|
}
|
|
65
89
|
// Focus first focusable element, or the container if none
|
|
66
90
|
requestAnimationFrame(() => {
|
|
91
|
+
const initialFocusTarget = resolveInitialFocus(node, initialFocus);
|
|
92
|
+
if (initialFocusTarget && initialFocusTarget.isConnected) {
|
|
93
|
+
initialFocusTarget.focus();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
67
96
|
const focusableElements = getFocusableElements(node);
|
|
68
97
|
if (focusableElements.length > 0) {
|
|
69
98
|
focusableElements[0].focus();
|
|
@@ -76,7 +105,7 @@ export function focusTrap(node, enabled = true) {
|
|
|
76
105
|
}
|
|
77
106
|
function deactivate() {
|
|
78
107
|
document.removeEventListener('keydown', handleKeydown, true);
|
|
79
|
-
if (previousActiveElement && previousActiveElement.focus) {
|
|
108
|
+
if (restoreFocus && previousActiveElement && previousActiveElement.focus) {
|
|
80
109
|
previousActiveElement.focus();
|
|
81
110
|
}
|
|
82
111
|
}
|
|
@@ -84,14 +113,19 @@ export function focusTrap(node, enabled = true) {
|
|
|
84
113
|
activate();
|
|
85
114
|
}
|
|
86
115
|
return {
|
|
87
|
-
update(
|
|
88
|
-
|
|
116
|
+
update(newOptions) {
|
|
117
|
+
const nextEnabled = resolveEnabled(newOptions);
|
|
118
|
+
const nextRestoreFocus = resolveRestoreFocus(newOptions);
|
|
119
|
+
if (nextEnabled && !enabled) {
|
|
89
120
|
activate();
|
|
90
121
|
}
|
|
91
|
-
else if (!
|
|
122
|
+
else if (!nextEnabled && enabled) {
|
|
92
123
|
deactivate();
|
|
93
124
|
}
|
|
94
|
-
|
|
125
|
+
options = newOptions;
|
|
126
|
+
enabled = nextEnabled;
|
|
127
|
+
restoreFocus = nextRestoreFocus;
|
|
128
|
+
initialFocus = typeof newOptions === 'boolean' ? undefined : newOptions.initialFocus;
|
|
95
129
|
},
|
|
96
130
|
destroy() {
|
|
97
131
|
if (enabled) {
|
package/dist/primitives/index.js
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type InputModality = 'keyboard' | 'pointer' | 'virtual';
|
|
2
|
+
export declare function initInputModality(target?: HTMLElement | null): void;
|
|
3
|
+
export declare function trackInteractionModality(event?: Event, target?: HTMLElement | null): void;
|
|
4
|
+
export declare function getInteractionModality(): InputModality;
|
|
5
|
+
export declare function shouldShowFocusVisible(target: HTMLElement | null): boolean;
|
|
6
|
+
export declare function focusWithModality(target: HTMLElement, modality: InputModality, options?: FocusOptions): void;
|
|
7
|
+
export declare function resolveCloseInteractionModality(reason: string, event?: Event): InputModality;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
let currentModality = 'virtual';
|
|
2
|
+
const listenedWindows = new WeakSet();
|
|
3
|
+
let forcedFocusTarget = null;
|
|
4
|
+
let forcedFocusModality = null;
|
|
5
|
+
function isModifierOnlyKey(event) {
|
|
6
|
+
return (event.key === 'Shift' || event.key === 'Control' || event.key === 'Alt' || event.key === 'Meta');
|
|
7
|
+
}
|
|
8
|
+
function isKeyboardModalityKey(event) {
|
|
9
|
+
if (event.ctrlKey || event.metaKey || event.altKey)
|
|
10
|
+
return false;
|
|
11
|
+
if (isModifierOnlyKey(event))
|
|
12
|
+
return false;
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
function ensureWindowListeners(win) {
|
|
16
|
+
if (!win)
|
|
17
|
+
return;
|
|
18
|
+
if (listenedWindows.has(win))
|
|
19
|
+
return;
|
|
20
|
+
const onKeyDown = (event) => {
|
|
21
|
+
if (!isKeyboardModalityKey(event))
|
|
22
|
+
return;
|
|
23
|
+
currentModality = 'keyboard';
|
|
24
|
+
};
|
|
25
|
+
const onPointerDown = () => {
|
|
26
|
+
currentModality = 'pointer';
|
|
27
|
+
};
|
|
28
|
+
const onFocusIn = () => {
|
|
29
|
+
if (currentModality === 'pointer' || currentModality === 'keyboard')
|
|
30
|
+
return;
|
|
31
|
+
currentModality = 'virtual';
|
|
32
|
+
};
|
|
33
|
+
win.addEventListener('keydown', onKeyDown, true);
|
|
34
|
+
win.addEventListener('pointerdown', onPointerDown, true);
|
|
35
|
+
win.addEventListener('mousedown', onPointerDown, true);
|
|
36
|
+
win.addEventListener('focusin', onFocusIn, true);
|
|
37
|
+
listenedWindows.add(win);
|
|
38
|
+
}
|
|
39
|
+
export function initInputModality(target) {
|
|
40
|
+
const ownerWindow = target?.ownerDocument?.defaultView;
|
|
41
|
+
if (ownerWindow) {
|
|
42
|
+
ensureWindowListeners(ownerWindow);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (typeof window !== 'undefined') {
|
|
46
|
+
ensureWindowListeners(window);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function inferModalityFromEvent(event) {
|
|
50
|
+
if (!event)
|
|
51
|
+
return undefined;
|
|
52
|
+
if (event instanceof KeyboardEvent) {
|
|
53
|
+
return isKeyboardModalityKey(event) ? 'keyboard' : undefined;
|
|
54
|
+
}
|
|
55
|
+
if (event instanceof MouseEvent && event.type === 'click' && event.detail === 0) {
|
|
56
|
+
return 'keyboard';
|
|
57
|
+
}
|
|
58
|
+
if (event instanceof PointerEvent || event instanceof MouseEvent) {
|
|
59
|
+
return 'pointer';
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
function resolveModalityForTarget(target) {
|
|
64
|
+
if (target && forcedFocusTarget === target && forcedFocusModality) {
|
|
65
|
+
const modality = forcedFocusModality;
|
|
66
|
+
forcedFocusTarget = null;
|
|
67
|
+
forcedFocusModality = null;
|
|
68
|
+
return modality;
|
|
69
|
+
}
|
|
70
|
+
return currentModality;
|
|
71
|
+
}
|
|
72
|
+
export function trackInteractionModality(event, target) {
|
|
73
|
+
initInputModality(target);
|
|
74
|
+
const inferred = inferModalityFromEvent(event);
|
|
75
|
+
if (inferred) {
|
|
76
|
+
currentModality = inferred;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export function getInteractionModality() {
|
|
80
|
+
return currentModality;
|
|
81
|
+
}
|
|
82
|
+
export function shouldShowFocusVisible(target) {
|
|
83
|
+
if (!target)
|
|
84
|
+
return false;
|
|
85
|
+
const modality = resolveModalityForTarget(target);
|
|
86
|
+
if (modality === 'pointer')
|
|
87
|
+
return false;
|
|
88
|
+
return target.matches(':focus-visible');
|
|
89
|
+
}
|
|
90
|
+
export function focusWithModality(target, modality, options) {
|
|
91
|
+
initInputModality(target);
|
|
92
|
+
// The forced target/modality pair is a synchronous safety net so a focus handler that runs
|
|
93
|
+
// immediately after target.focus() can resolve the intended modality without racing async timing.
|
|
94
|
+
// If it is consumed later, currentModality still preserves the same modality as fallback.
|
|
95
|
+
forcedFocusTarget = target;
|
|
96
|
+
forcedFocusModality = modality;
|
|
97
|
+
currentModality = modality;
|
|
98
|
+
target.focus(options);
|
|
99
|
+
queueMicrotask(() => {
|
|
100
|
+
if (forcedFocusTarget !== target)
|
|
101
|
+
return;
|
|
102
|
+
forcedFocusTarget = null;
|
|
103
|
+
forcedFocusModality = null;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
export function resolveCloseInteractionModality(reason, event) {
|
|
107
|
+
if (reason === 'escape-key')
|
|
108
|
+
return 'keyboard';
|
|
109
|
+
if (event instanceof KeyboardEvent)
|
|
110
|
+
return 'keyboard';
|
|
111
|
+
if (event instanceof PointerEvent || event instanceof MouseEvent)
|
|
112
|
+
return 'pointer';
|
|
113
|
+
if (reason === 'outside-press')
|
|
114
|
+
return 'pointer';
|
|
115
|
+
return 'virtual';
|
|
116
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { expect } from 'vitest';
|
|
2
|
+
export function expectFocusVisibleImpliesFocusWithin(root) {
|
|
3
|
+
expect(root).toBeTruthy();
|
|
4
|
+
const focusVisible = root?.getAttribute('data-focus-visible');
|
|
5
|
+
const focusWithin = root?.getAttribute('data-focus-within');
|
|
6
|
+
if (focusVisible === 'true') {
|
|
7
|
+
expect(focusWithin).toBe('true');
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function expectFocusVisibleImpliesFocused(node) {
|
|
11
|
+
expect(node).toBeTruthy();
|
|
12
|
+
const focusVisible = node?.getAttribute('data-focus-visible');
|
|
13
|
+
const focused = node?.getAttribute('data-focused');
|
|
14
|
+
if (focusVisible === 'true') {
|
|
15
|
+
expect(focused).toBe('true');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function expectNoFalseFocusAttributes(scope = document) {
|
|
19
|
+
const focusAttrs = ['data-focused', 'data-focus-visible', 'data-focus-within'];
|
|
20
|
+
for (const attr of focusAttrs) {
|
|
21
|
+
const nodes = Array.from(scope.querySelectorAll(`[${attr}]`));
|
|
22
|
+
for (const node of nodes) {
|
|
23
|
+
expect(node.getAttribute(attr)).not.toBe('false');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type DateOnlyValue = string;
|
|
2
|
+
export declare function createUtcDate(year: number, monthIndex: number, day: number): Date;
|
|
3
|
+
export declare function parseDateOnlyParts(value: string): {
|
|
4
|
+
year: number;
|
|
5
|
+
month: number;
|
|
6
|
+
day: number;
|
|
7
|
+
} | null;
|
|
8
|
+
export declare function isValidDateOnlyValue(value: string | undefined): value is DateOnlyValue;
|
|
9
|
+
export declare function parseDateOnlyValue(value: string): Date | null;
|
|
10
|
+
export declare function formatDateOnlyValue(date: Date): DateOnlyValue;
|
|
11
|
+
export declare function compareDateOnlyValues(left: DateOnlyValue, right: DateOnlyValue): number;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const DATE_ONLY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
2
|
+
export function createUtcDate(year, monthIndex, day) {
|
|
3
|
+
const date = new Date(Date.UTC(0, 0, 1));
|
|
4
|
+
date.setUTCHours(0, 0, 0, 0);
|
|
5
|
+
date.setUTCFullYear(year, monthIndex, day);
|
|
6
|
+
return date;
|
|
7
|
+
}
|
|
8
|
+
export function parseDateOnlyParts(value) {
|
|
9
|
+
const match = DATE_ONLY_RE.exec(value);
|
|
10
|
+
if (!match)
|
|
11
|
+
return null;
|
|
12
|
+
const year = Number(match[1]);
|
|
13
|
+
const month = Number(match[2]);
|
|
14
|
+
const day = Number(match[3]);
|
|
15
|
+
if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day))
|
|
16
|
+
return null;
|
|
17
|
+
return { year, month, day };
|
|
18
|
+
}
|
|
19
|
+
export function isValidDateOnlyValue(value) {
|
|
20
|
+
if (!value)
|
|
21
|
+
return false;
|
|
22
|
+
const parts = parseDateOnlyParts(value);
|
|
23
|
+
if (!parts)
|
|
24
|
+
return false;
|
|
25
|
+
const { year, month, day } = parts;
|
|
26
|
+
if (month < 1 || month > 12 || day < 1 || day > 31)
|
|
27
|
+
return false;
|
|
28
|
+
const date = createUtcDate(year, month - 1, day);
|
|
29
|
+
return (date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day);
|
|
30
|
+
}
|
|
31
|
+
export function parseDateOnlyValue(value) {
|
|
32
|
+
if (!isValidDateOnlyValue(value))
|
|
33
|
+
return null;
|
|
34
|
+
const parts = parseDateOnlyParts(value);
|
|
35
|
+
if (!parts)
|
|
36
|
+
return null;
|
|
37
|
+
return createUtcDate(parts.year, parts.month - 1, parts.day);
|
|
38
|
+
}
|
|
39
|
+
export function formatDateOnlyValue(date) {
|
|
40
|
+
const year = String(date.getUTCFullYear()).padStart(4, '0');
|
|
41
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
|
|
42
|
+
const day = String(date.getUTCDate()).padStart(2, '0');
|
|
43
|
+
return `${year}-${month}-${day}`;
|
|
44
|
+
}
|
|
45
|
+
export function compareDateOnlyValues(left, right) {
|
|
46
|
+
const leftDate = parseDateOnlyValue(left);
|
|
47
|
+
const rightDate = parseDateOnlyValue(right);
|
|
48
|
+
if (!leftDate || !rightDate)
|
|
49
|
+
return 0;
|
|
50
|
+
if (leftDate.getTime() === rightDate.getTime())
|
|
51
|
+
return 0;
|
|
52
|
+
return leftDate.getTime() < rightDate.getTime() ? -1 : 1;
|
|
53
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@human-kit/svelte-components",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"svelte": "./dist/index.js",
|
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
"svelte": "./dist/combobox/index.js",
|
|
17
17
|
"default": "./dist/combobox/index.js"
|
|
18
18
|
},
|
|
19
|
+
"./calendar": {
|
|
20
|
+
"types": "./dist/calendar/index.d.ts",
|
|
21
|
+
"svelte": "./dist/calendar/index.js",
|
|
22
|
+
"default": "./dist/calendar/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./datepicker": {
|
|
25
|
+
"types": "./dist/datepicker/index.d.ts",
|
|
26
|
+
"svelte": "./dist/datepicker/index.js",
|
|
27
|
+
"default": "./dist/datepicker/index.js"
|
|
28
|
+
},
|
|
19
29
|
"./dialog": {
|
|
20
30
|
"types": "./dist/dialog/index.d.ts",
|
|
21
31
|
"svelte": "./dist/dialog/index.js",
|
|
@@ -41,6 +51,11 @@
|
|
|
41
51
|
"svelte": "./dist/label/index.js",
|
|
42
52
|
"default": "./dist/label/index.js"
|
|
43
53
|
},
|
|
54
|
+
"./locale-provider": {
|
|
55
|
+
"types": "./dist/locale-provider/index.d.ts",
|
|
56
|
+
"svelte": "./dist/locale-provider/index.js",
|
|
57
|
+
"default": "./dist/locale-provider/index.js"
|
|
58
|
+
},
|
|
44
59
|
"./portal": {
|
|
45
60
|
"types": "./dist/portal/index.d.ts",
|
|
46
61
|
"svelte": "./dist/portal/index.js",
|