@vielzeug/refine 1.5.4 → 1.7.0
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/accordion.cjs +1 -1
- package/dist/accordion.cjs.map +1 -1
- package/dist/accordion.js +30 -28
- package/dist/accordion.js.map +1 -1
- package/dist/button-group.cjs.map +1 -1
- package/dist/button-group.js.map +1 -1
- package/dist/button.cjs +1 -1
- package/dist/button.cjs.map +1 -1
- package/dist/button.js +1 -0
- package/dist/button.js.map +1 -1
- package/dist/checkbox-group.cjs +3 -3
- package/dist/checkbox-group.cjs.map +1 -1
- package/dist/checkbox-group.js +51 -58
- package/dist/checkbox-group.js.map +1 -1
- package/dist/combobox.cjs +29 -28
- package/dist/combobox.cjs.map +1 -1
- package/dist/combobox.js +201 -195
- package/dist/combobox.js.map +1 -1
- package/dist/content/card/card.css.cjs +1 -1
- package/dist/content/card/card.css.cjs.map +1 -1
- package/dist/content/card/card.css.js +1 -1
- package/dist/content/card/card.css.js.map +1 -1
- package/dist/content/chat-message/chat-message.css.cjs +1 -1
- package/dist/content/chat-message/chat-message.css.cjs.map +1 -1
- package/dist/content/chat-message/chat-message.css.js +1 -1
- package/dist/content/chat-message/chat-message.css.js.map +1 -1
- package/dist/content/icon/icon.d.ts.map +1 -1
- package/dist/content/index.d.ts +4 -0
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/list/_list-item-reveal.cjs +2 -0
- package/dist/content/list/_list-item-reveal.cjs.map +1 -0
- package/dist/content/list/_list-item-reveal.d.ts +25 -0
- package/dist/content/list/_list-item-reveal.d.ts.map +1 -0
- package/dist/content/list/_list-item-reveal.js +33 -0
- package/dist/content/list/_list-item-reveal.js.map +1 -0
- package/dist/content/list/list-item.css.cjs +2 -0
- package/dist/content/list/list-item.css.cjs.map +1 -0
- package/dist/content/list/list-item.css.js +6 -0
- package/dist/content/list/list-item.css.js.map +1 -0
- package/dist/content/list/list-item.d.ts +111 -0
- package/dist/content/list/list-item.d.ts.map +1 -0
- package/dist/content/list/list.css.cjs +2 -0
- package/dist/content/list/list.css.cjs.map +1 -0
- package/dist/content/list/list.css.js +6 -0
- package/dist/content/list/list.css.js.map +1 -0
- package/dist/content/list/list.d.ts +93 -0
- package/dist/content/list/list.d.ts.map +1 -0
- package/dist/custom-elements.json +6052 -5449
- package/dist/date-picker.cjs +2 -1
- package/dist/date-picker.cjs.map +1 -1
- package/dist/date-picker.js +2 -0
- package/dist/date-picker.js.map +1 -1
- package/dist/disclosure/tab-item/tab-item.d.ts.map +1 -1
- package/dist/disclosure/tab-panel/tab-panel.d.ts.map +1 -1
- package/dist/disclosure/tabs/tabs.d.ts.map +1 -1
- package/dist/drawer.cjs +1 -1
- package/dist/drawer.cjs.map +1 -1
- package/dist/drawer.js +47 -48
- package/dist/drawer.js.map +1 -1
- package/dist/file-input.cjs +111 -45
- package/dist/file-input.cjs.map +1 -1
- package/dist/file-input.js +216 -156
- package/dist/file-input.js.map +1 -1
- package/dist/headless/aria-reflection.cjs +2 -0
- package/dist/headless/aria-reflection.cjs.map +1 -0
- package/dist/headless/aria-reflection.d.ts +27 -0
- package/dist/headless/aria-reflection.d.ts.map +1 -0
- package/dist/headless/aria-reflection.js +18 -0
- package/dist/headless/aria-reflection.js.map +1 -0
- package/dist/headless/checkable.cjs +1 -1
- package/dist/headless/checkable.cjs.map +1 -1
- package/dist/headless/checkable.d.ts +2 -10
- package/dist/headless/checkable.d.ts.map +1 -1
- package/dist/headless/checkable.js +29 -29
- package/dist/headless/checkable.js.map +1 -1
- package/dist/headless/choice-field.cjs +1 -1
- package/dist/headless/choice-field.cjs.map +1 -1
- package/dist/headless/choice-field.d.ts +2 -7
- package/dist/headless/choice-field.d.ts.map +1 -1
- package/dist/headless/choice-field.js +37 -37
- package/dist/headless/choice-field.js.map +1 -1
- package/dist/headless/field-base.cjs +1 -1
- package/dist/headless/field-base.cjs.map +1 -1
- package/dist/headless/field-base.d.ts +17 -0
- package/dist/headless/field-base.d.ts.map +1 -1
- package/dist/headless/field-base.js +21 -6
- package/dist/headless/field-base.js.map +1 -1
- package/dist/headless/focus.cjs +1 -1
- package/dist/headless/focus.cjs.map +1 -1
- package/dist/headless/focus.d.ts +7 -0
- package/dist/headless/focus.d.ts.map +1 -1
- package/dist/headless/focus.js +6 -6
- package/dist/headless/focus.js.map +1 -1
- package/dist/headless/index.d.ts +4 -2
- package/dist/headless/index.d.ts.map +1 -1
- package/dist/headless/nav.cjs +1 -1
- package/dist/headless/nav.cjs.map +1 -1
- package/dist/headless/nav.d.ts +12 -0
- package/dist/headless/nav.d.ts.map +1 -1
- package/dist/headless/nav.js +68 -50
- package/dist/headless/nav.js.map +1 -1
- package/dist/headless/overlay.cjs +1 -1
- package/dist/headless/overlay.cjs.map +1 -1
- package/dist/headless/overlay.d.ts +2 -0
- package/dist/headless/overlay.d.ts.map +1 -1
- package/dist/headless/overlay.js +8 -5
- package/dist/headless/overlay.js.map +1 -1
- package/dist/headless/ref-callback.cjs +2 -0
- package/dist/headless/ref-callback.cjs.map +1 -0
- package/dist/headless/ref-callback.d.ts +26 -0
- package/dist/headless/ref-callback.d.ts.map +1 -0
- package/dist/headless/ref-callback.js +13 -0
- package/dist/headless/ref-callback.js.map +1 -0
- package/dist/headless/swipe.cjs +1 -1
- package/dist/headless/swipe.cjs.map +1 -1
- package/dist/headless/swipe.d.ts +2 -0
- package/dist/headless/swipe.d.ts.map +1 -1
- package/dist/headless/swipe.js +26 -21
- package/dist/headless/swipe.js.map +1 -1
- package/dist/headless.cjs +1 -1
- package/dist/headless.js +28 -26
- package/dist/icon.cjs +1 -1
- package/dist/icon.cjs.map +1 -1
- package/dist/icon.js +36 -38
- package/dist/icon.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +63 -61
- package/dist/input.cjs +29 -26
- package/dist/input.cjs.map +1 -1
- package/dist/input.js +91 -79
- package/dist/input.js.map +1 -1
- package/dist/inputs/button/button.css.cjs +1 -1
- package/dist/inputs/button/button.css.cjs.map +1 -1
- package/dist/inputs/button/button.css.js +1 -1
- package/dist/inputs/button/button.css.js.map +1 -1
- package/dist/inputs/button/button.d.ts +8 -0
- package/dist/inputs/button/button.d.ts.map +1 -1
- package/dist/inputs/checkbox/checkbox.css.cjs +1 -1
- package/dist/inputs/checkbox/checkbox.css.cjs.map +1 -1
- package/dist/inputs/checkbox/checkbox.css.js +1 -1
- package/dist/inputs/checkbox/checkbox.css.js.map +1 -1
- package/dist/inputs/combobox/combobox.d.ts +1 -0
- package/dist/inputs/combobox/combobox.d.ts.map +1 -1
- package/dist/inputs/date-picker/date-picker.css.cjs +1 -1
- package/dist/inputs/date-picker/date-picker.css.cjs.map +1 -1
- package/dist/inputs/date-picker/date-picker.css.js +1 -1
- package/dist/inputs/date-picker/date-picker.css.js.map +1 -1
- package/dist/inputs/date-picker/date-picker.d.ts +6 -0
- package/dist/inputs/date-picker/date-picker.d.ts.map +1 -1
- package/dist/inputs/file-input/file-input-upload.cjs +2 -0
- package/dist/inputs/file-input/file-input-upload.cjs.map +1 -0
- package/dist/inputs/file-input/file-input-upload.d.ts +76 -0
- package/dist/inputs/file-input/file-input-upload.d.ts.map +1 -0
- package/dist/inputs/file-input/file-input-upload.js +145 -0
- package/dist/inputs/file-input/file-input-upload.js.map +1 -0
- package/dist/inputs/file-input/file-input.css.cjs +1 -1
- package/dist/inputs/file-input/file-input.css.cjs.map +1 -1
- package/dist/inputs/file-input/file-input.css.js +1 -1
- package/dist/inputs/file-input/file-input.css.js.map +1 -1
- package/dist/inputs/file-input/file-input.d.ts +54 -1
- package/dist/inputs/file-input/file-input.d.ts.map +1 -1
- package/dist/inputs/input/input.css.cjs +1 -1
- package/dist/inputs/input/input.css.cjs.map +1 -1
- package/dist/inputs/input/input.css.js +1 -1
- package/dist/inputs/input/input.css.js.map +1 -1
- package/dist/inputs/input/input.d.ts +13 -0
- package/dist/inputs/input/input.d.ts.map +1 -1
- package/dist/inputs/message-composer/message-composer.css.cjs +1 -1
- package/dist/inputs/message-composer/message-composer.css.cjs.map +1 -1
- package/dist/inputs/message-composer/message-composer.css.js +1 -1
- package/dist/inputs/message-composer/message-composer.css.js.map +1 -1
- package/dist/inputs/message-composer/message-composer.d.ts +7 -0
- package/dist/inputs/message-composer/message-composer.d.ts.map +1 -1
- package/dist/inputs/number-input/number-input.css.cjs +1 -1
- package/dist/inputs/number-input/number-input.css.cjs.map +1 -1
- package/dist/inputs/number-input/number-input.css.js +1 -1
- package/dist/inputs/number-input/number-input.css.js.map +1 -1
- package/dist/inputs/number-input/number-input.d.ts +15 -0
- package/dist/inputs/number-input/number-input.d.ts.map +1 -1
- package/dist/inputs/otp-input/otp-input.css.cjs +1 -1
- package/dist/inputs/otp-input/otp-input.css.cjs.map +1 -1
- package/dist/inputs/otp-input/otp-input.css.js +1 -1
- package/dist/inputs/otp-input/otp-input.css.js.map +1 -1
- package/dist/inputs/radio/radio.d.ts.map +1 -1
- package/dist/inputs/select/select.d.ts +1 -0
- package/dist/inputs/select/select.d.ts.map +1 -1
- package/dist/inputs/shared/index.d.ts +1 -1
- package/dist/inputs/shared/index.d.ts.map +1 -1
- package/dist/inputs/shared/templates.cjs +26 -3
- package/dist/inputs/shared/templates.cjs.map +1 -1
- package/dist/inputs/shared/templates.d.ts +20 -0
- package/dist/inputs/shared/templates.d.ts.map +1 -1
- package/dist/inputs/shared/templates.js +29 -5
- package/dist/inputs/shared/templates.js.map +1 -1
- package/dist/inputs/switch/switch.css.cjs +1 -1
- package/dist/inputs/switch/switch.css.cjs.map +1 -1
- package/dist/inputs/switch/switch.css.js +1 -1
- package/dist/inputs/switch/switch.css.js.map +1 -1
- package/dist/inputs/switch/switch.d.ts.map +1 -1
- package/dist/inputs/textarea/textarea.css.cjs +1 -1
- package/dist/inputs/textarea/textarea.css.cjs.map +1 -1
- package/dist/inputs/textarea/textarea.css.js +1 -1
- package/dist/inputs/textarea/textarea.css.js.map +1 -1
- package/dist/inputs/textarea/textarea.d.ts +14 -0
- package/dist/inputs/textarea/textarea.d.ts.map +1 -1
- package/dist/inputs/time-picker/time-picker.css.cjs +1 -1
- package/dist/inputs/time-picker/time-picker.css.cjs.map +1 -1
- package/dist/inputs/time-picker/time-picker.css.js +1 -1
- package/dist/inputs/time-picker/time-picker.css.js.map +1 -1
- package/dist/inputs/time-picker/time-picker.d.ts +6 -0
- package/dist/inputs/time-picker/time-picker.d.ts.map +1 -1
- package/dist/list-item.cjs +22 -0
- package/dist/list-item.cjs.map +1 -0
- package/dist/list-item.js +91 -0
- package/dist/list-item.js.map +1 -0
- package/dist/list.cjs +2 -0
- package/dist/list.cjs.map +1 -0
- package/dist/list.js +64 -0
- package/dist/list.js.map +1 -0
- package/dist/menu.cjs +4 -4
- package/dist/menu.cjs.map +1 -1
- package/dist/menu.js +60 -59
- package/dist/menu.js.map +1 -1
- package/dist/message-composer.cjs +14 -20
- package/dist/message-composer.cjs.map +1 -1
- package/dist/message-composer.js +63 -64
- package/dist/message-composer.js.map +1 -1
- package/dist/number-input.cjs +5 -3
- package/dist/number-input.cjs.map +1 -1
- package/dist/number-input.js +8 -4
- package/dist/number-input.js.map +1 -1
- package/dist/otp-input.cjs +9 -9
- package/dist/otp-input.cjs.map +1 -1
- package/dist/otp-input.js +70 -62
- package/dist/otp-input.js.map +1 -1
- package/dist/overlay/menu/menu-item.css.cjs +1 -1
- package/dist/overlay/menu/menu-item.css.cjs.map +1 -1
- package/dist/overlay/menu/menu-item.css.js +1 -1
- package/dist/overlay/menu/menu-item.css.js.map +1 -1
- package/dist/overlay/menu/menu-separator.css.cjs +1 -1
- package/dist/overlay/menu/menu-separator.css.cjs.map +1 -1
- package/dist/overlay/menu/menu-separator.css.js +1 -1
- package/dist/overlay/menu/menu-separator.css.js.map +1 -1
- package/dist/overlay/menu/menu.css.cjs +1 -1
- package/dist/overlay/menu/menu.css.cjs.map +1 -1
- package/dist/overlay/menu/menu.css.js +1 -1
- package/dist/overlay/menu/menu.css.js.map +1 -1
- package/dist/overlay/shared/use-dialog.cjs +1 -1
- package/dist/overlay/shared/use-dialog.cjs.map +1 -1
- package/dist/overlay/shared/use-dialog.d.ts.map +1 -1
- package/dist/overlay/shared/use-dialog.js +7 -6
- package/dist/overlay/shared/use-dialog.js.map +1 -1
- package/dist/radio-group.cjs +3 -3
- package/dist/radio-group.cjs.map +1 -1
- package/dist/radio-group.js +64 -70
- package/dist/radio-group.js.map +1 -1
- package/dist/radio.cjs +4 -4
- package/dist/radio.cjs.map +1 -1
- package/dist/radio.js +58 -58
- package/dist/radio.js.map +1 -1
- package/dist/rating.cjs.map +1 -1
- package/dist/rating.js.map +1 -1
- package/dist/refine.cjs +426 -233
- package/dist/refine.cjs.map +1 -1
- package/dist/refine.iife.js +431 -238
- package/dist/refine.iife.js.map +1 -1
- package/dist/refine.js +452 -259
- package/dist/refine.js.map +1 -1
- package/dist/select.cjs +21 -21
- package/dist/select.cjs.map +1 -1
- package/dist/select.js +135 -127
- package/dist/select.js.map +1 -1
- package/dist/shared/size-presets.cjs +1 -1
- package/dist/shared/size-presets.cjs.map +1 -1
- package/dist/shared/size-presets.d.ts +74 -0
- package/dist/shared/size-presets.d.ts.map +1 -1
- package/dist/shared/size-presets.js +48 -1
- package/dist/shared/size-presets.js.map +1 -1
- package/dist/shared/types.d.ts +5 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/styles/index.cjs +1 -1
- package/dist/styles/index.cjs.map +1 -1
- package/dist/styles/index.d.ts +1 -0
- package/dist/styles/index.d.ts.map +1 -1
- package/dist/styles/index.js +5 -4
- package/dist/styles/index.js.map +1 -1
- package/dist/styles/mixins/field-variants.css.cjs +85 -0
- package/dist/styles/mixins/field-variants.css.cjs.map +1 -0
- package/dist/styles/mixins/field-variants.css.d.ts +66 -0
- package/dist/styles/mixins/field-variants.css.d.ts.map +1 -0
- package/dist/styles/mixins/field-variants.css.js +90 -0
- package/dist/styles/mixins/field-variants.css.js.map +1 -0
- package/dist/switch.cjs +3 -3
- package/dist/switch.cjs.map +1 -1
- package/dist/switch.js +38 -36
- package/dist/switch.js.map +1 -1
- package/dist/tab-item.cjs +8 -8
- package/dist/tab-item.cjs.map +1 -1
- package/dist/tab-item.js +38 -28
- package/dist/tab-item.js.map +1 -1
- package/dist/tab-panel.cjs +7 -7
- package/dist/tab-panel.cjs.map +1 -1
- package/dist/tab-panel.js +34 -26
- package/dist/tab-panel.js.map +1 -1
- package/dist/tabs.cjs +4 -4
- package/dist/tabs.cjs.map +1 -1
- package/dist/tabs.js +64 -60
- package/dist/tabs.js.map +1 -1
- package/dist/textarea.cjs +15 -18
- package/dist/textarea.cjs.map +1 -1
- package/dist/textarea.js +83 -74
- package/dist/textarea.js.map +1 -1
- package/dist/time-picker.cjs +34 -31
- package/dist/time-picker.cjs.map +1 -1
- package/dist/time-picker.js +121 -109
- package/dist/time-picker.js.map +1 -1
- package/package.json +21 -9
- package/dist/headless/dialog-focus.cjs +0 -2
- package/dist/headless/dialog-focus.cjs.map +0 -1
- package/dist/headless/dialog-focus.d.ts +0 -40
- package/dist/headless/dialog-focus.d.ts.map +0 -1
- package/dist/headless/dialog-focus.js +0 -22
- package/dist/headless/dialog-focus.js.map +0 -1
- package/dist/headless/focus-trap.cjs +0 -2
- package/dist/headless/focus-trap.cjs.map +0 -1
- package/dist/headless/focus-trap.d.ts +0 -45
- package/dist/headless/focus-trap.d.ts.map +0 -1
- package/dist/headless/focus-trap.js +0 -51
- package/dist/headless/focus-trap.js.map +0 -1
package/dist/headless/nav.js
CHANGED
|
@@ -27,42 +27,56 @@ var r = (e, t, n) => {
|
|
|
27
27
|
next: ["ArrowDown"],
|
|
28
28
|
prev: ["ArrowUp"]
|
|
29
29
|
}
|
|
30
|
-
}, o =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
}, o = {
|
|
31
|
+
both: {
|
|
32
|
+
first: ["Home"],
|
|
33
|
+
last: ["End"],
|
|
34
|
+
next: ["ArrowDown", "ArrowLeft"],
|
|
35
|
+
prev: ["ArrowUp", "ArrowRight"]
|
|
36
|
+
},
|
|
37
|
+
horizontal: {
|
|
38
|
+
first: ["Home"],
|
|
39
|
+
last: ["End"],
|
|
40
|
+
next: ["ArrowLeft"],
|
|
41
|
+
prev: ["ArrowRight"]
|
|
42
|
+
},
|
|
43
|
+
vertical: a.vertical
|
|
44
|
+
}, s = (e) => typeof e == "object" && !!e && "disabled" in e, c = (c) => {
|
|
45
|
+
let l = n(-1), u = (e, t) => c.isItemDisabled?.(e, t) ?? (s(e) ? e.disabled : !1), d = (e, t, n) => (e >= 0 && (l.value = e, c.onNavigate?.(t, e, n)), e), f = (e, t, n) => n === "forward" ? r(e, t, (e, t) => !u(e, t)) : i(e, t, (e, t) => !u(e, t)), p = (e) => {
|
|
46
|
+
if (e < 0) return _(), -1;
|
|
47
|
+
let t = c.getItems();
|
|
34
48
|
if (!t.length) return -1;
|
|
35
49
|
let n = Math.min(e, t.length - 1);
|
|
36
|
-
return
|
|
37
|
-
},
|
|
38
|
-
let n =
|
|
50
|
+
return u(t[n], n) ? -1 : (l.value = n, n);
|
|
51
|
+
}, m = (e, t) => {
|
|
52
|
+
let n = c.getItems(), r = l.value;
|
|
39
53
|
if (!n.length) return -1;
|
|
40
|
-
let i = r < 0 ? e === "forward" ? 0 : n.length - 1 : e === "forward" ? r + 1 : r - 1, a =
|
|
41
|
-
if (a >= 0) return
|
|
42
|
-
if (
|
|
43
|
-
let r = e === "forward" ? 0 : n.length - 1, i =
|
|
44
|
-
if (i >= 0) return
|
|
54
|
+
let i = r < 0 ? e === "forward" ? 0 : n.length - 1 : e === "forward" ? r + 1 : r - 1, a = f(n, i, e), o = e === "forward" ? "next" : "prev";
|
|
55
|
+
if (a >= 0) return d(a, o, t);
|
|
56
|
+
if (c.loop) {
|
|
57
|
+
let r = e === "forward" ? 0 : n.length - 1, i = f(n, r, e);
|
|
58
|
+
if (i >= 0) return d(i, o, t);
|
|
45
59
|
}
|
|
46
60
|
return r;
|
|
47
|
-
},
|
|
48
|
-
let t =
|
|
61
|
+
}, h = (e) => {
|
|
62
|
+
let t = c.getItems();
|
|
49
63
|
if (!t.length) return -1;
|
|
50
64
|
if (e === "first") {
|
|
51
|
-
let e =
|
|
52
|
-
return
|
|
65
|
+
let e = f(t, 0, "forward");
|
|
66
|
+
return d(e, "first");
|
|
53
67
|
}
|
|
54
68
|
if (e === "last") {
|
|
55
|
-
let e =
|
|
56
|
-
return
|
|
69
|
+
let e = f(t, t.length - 1, "backward");
|
|
70
|
+
return d(e, "last");
|
|
57
71
|
}
|
|
58
|
-
return
|
|
59
|
-
}, h = () => {
|
|
60
|
-
let e = s.getItems(), t = c.value;
|
|
61
|
-
return t >= 0 && t < e.length ? e[t] : void 0;
|
|
72
|
+
return m(e === "next" ? "forward" : "backward");
|
|
62
73
|
}, g = () => {
|
|
63
|
-
c.
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
let e = c.getItems(), t = l.value;
|
|
75
|
+
return t >= 0 && t < e.length ? e[t] : void 0;
|
|
76
|
+
}, _ = () => {
|
|
77
|
+
l.value = -1;
|
|
78
|
+
}, v = () => !!c.disabled?.value, y = () => typeof c.orientation == "function" ? c.orientation() : c.orientation ?? "vertical", b = () => typeof c.direction == "function" ? c.direction() : c.direction ?? "ltr", x = () => {
|
|
79
|
+
let e = c.keys, t = (b() === "rtl" ? o : a)[y()], n = {
|
|
66
80
|
first: e?.first ?? t.first,
|
|
67
81
|
last: e?.last ?? t.last,
|
|
68
82
|
next: e?.next ?? t.next,
|
|
@@ -75,41 +89,45 @@ var r = (e, t, n) => {
|
|
|
75
89
|
"last"
|
|
76
90
|
]) for (let t of n[e]) r[t] = (t) => {
|
|
77
91
|
if (e === "first" || e === "last") {
|
|
78
|
-
let n =
|
|
92
|
+
let n = c.getItems();
|
|
79
93
|
if (!n.length) return;
|
|
80
|
-
let r = e === "first" ?
|
|
81
|
-
|
|
82
|
-
} else
|
|
94
|
+
let r = e === "first" ? f(n, 0, "forward") : f(n, n.length - 1, "backward");
|
|
95
|
+
d(r, e, t);
|
|
96
|
+
} else m(e === "next" ? "forward" : "backward", t);
|
|
83
97
|
};
|
|
84
98
|
return r;
|
|
85
|
-
},
|
|
86
|
-
getIndex: () =>
|
|
87
|
-
getItemLabel:
|
|
88
|
-
getItems:
|
|
89
|
-
isItemDisabled: (e, t) =>
|
|
99
|
+
}, S = c.getItemLabel ? t({
|
|
100
|
+
getIndex: () => l.value,
|
|
101
|
+
getItemLabel: c.getItemLabel,
|
|
102
|
+
getItems: c.getItems,
|
|
103
|
+
isItemDisabled: (e, t) => u(e, t),
|
|
90
104
|
onNavigate: (e, t) => {
|
|
91
|
-
|
|
105
|
+
v() || (l.value = e, c.onNavigate?.("typeahead", e, t));
|
|
92
106
|
}
|
|
93
|
-
}) : null,
|
|
94
|
-
let n =
|
|
107
|
+
}) : null, C = typeof c.orientation == "function" || typeof c.direction == "function" ? null : x(), w = (t) => {
|
|
108
|
+
let n = C ?? x();
|
|
95
109
|
return e(t, {
|
|
96
|
-
disabled:
|
|
110
|
+
disabled: c.disabled,
|
|
97
111
|
keymap: n
|
|
98
|
-
}) ? !0 :
|
|
99
|
-
},
|
|
100
|
-
|
|
112
|
+
}) ? !0 : S?.handleKeydown(t) ?? !1;
|
|
113
|
+
}, T = !1, E = () => {
|
|
114
|
+
T || (T = !0, S?.reset());
|
|
101
115
|
};
|
|
102
|
-
return
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
return c.signal && c.signal.addEventListener("abort", E, { once: !0 }), {
|
|
117
|
+
dispose: E,
|
|
118
|
+
get disposed() {
|
|
119
|
+
return T;
|
|
120
|
+
},
|
|
121
|
+
focusedIndex: l,
|
|
122
|
+
getActiveItem: g,
|
|
123
|
+
handleKeydown: w,
|
|
124
|
+
navigate: h,
|
|
125
|
+
reset: _,
|
|
126
|
+
set: p,
|
|
127
|
+
[Symbol.dispose]: E
|
|
110
128
|
};
|
|
111
129
|
};
|
|
112
130
|
//#endregion
|
|
113
|
-
export {
|
|
131
|
+
export { c as createListControl };
|
|
114
132
|
|
|
115
133
|
//# sourceMappingURL=nav.js.map
|
package/dist/headless/nav.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nav.js","names":[],"sources":["../../src/headless/nav.ts"],"sourcesContent":["import { type Readable, signal } from '@vielzeug/ripple';\n\nimport { dispatchKeyboardAction } from './keyboard';\nimport { createTypeahead } from './typeahead';\n\n// ── Private array search helpers ────────────────────────────────────────────\nconst findForward = <T>(items: T[], start: number, predicate: (item: T, index: number) => boolean): number => {\n for (let idx = start; idx < items.length; idx++) {\n if (predicate(items[idx], idx)) return idx;\n }\n\n return -1;\n};\n\nconst findBackward = <T>(items: T[], start: number, predicate: (item: T, index: number) => boolean): number => {\n for (let idx = start; idx >= 0; idx--) {\n if (predicate(items[idx], idx)) return idx;\n }\n\n return -1;\n};\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/**\n * Arrow-key and Home/End navigation actions. Separate from `ListKeyAction` so\n * keymap declarations never need `Exclude<ListKeyAction, 'typeahead'>`.\n */\nexport type ListNavigationAction = 'first' | 'last' | 'next' | 'prev';\n\n/**\n * All possible navigation action names, including `'typeahead'` (character-key\n * search). Used in `onNavigate` callbacks where both nav and typeahead are valid.\n */\nexport type ListKeyAction = ListNavigationAction | 'typeahead';\n\n/**\n * Orientation-aware default key bindings for list navigation.\n * - `'vertical'` (default): Up/Down arrows, Home/End\n * - `'horizontal'`: Left/Right arrows, Home/End\n * - `'both'`: all four arrow keys + Home/End\n */\nconst DEFAULT_KEYS: Record<'both' | 'horizontal' | 'vertical', Record<ListNavigationAction, string[]>> = {\n both: { first: ['Home'], last: ['End'], next: ['ArrowDown', 'ArrowRight'], prev: ['ArrowUp', 'ArrowLeft'] },\n horizontal: { first: ['Home'], last: ['End'], next: ['ArrowRight'], prev: ['ArrowLeft'] },\n vertical: { first: ['Home'], last: ['End'], next: ['ArrowDown'], prev: ['ArrowUp'] },\n};\n\nexport type ListNavigationOptions<T> = {\n disabled?: Readable<boolean | undefined>;\n /**\n * Returns a plain-text label for an item used by typeahead search.\n * When provided, pressing a printable character key jumps to the first enabled\n * item whose label starts with the accumulated type buffer (500 ms reset window).\n * Omit for contexts where the host element is a text input (e.g. combobox).\n */\n getItemLabel?: (item: T, index: number) => string;\n getItems: () => T[];\n isItemDisabled?: (item: T, index: number) => boolean;\n /** Override default key bindings for navigation actions. */\n keys?: Partial<Record<ListNavigationAction, string[]>>;\n loop?: boolean;\n /**\n * Called when navigation lands on a new index.\n * `event` is the originating `KeyboardEvent`, or `undefined` for programmatic navigation.\n * Use to sync side-effects such as focusing a DOM element.\n */\n onNavigate?: (action: ListKeyAction, index: number, event?: KeyboardEvent) => void;\n /**\n * Axis the list navigates along. Controls which arrow keys are treated as\n * `next`/`prev` when no explicit `keys` override is provided.\n * Pass a getter function for dynamic/reactive orientation (e.g. driven by a prop signal).\n *\n * - `'vertical'` (default): ↑↓ arrows\n * - `'horizontal'`: ←→ arrows\n * - `'both'`: all four arrow keys\n */\n orientation?: 'both' | 'horizontal' | 'vertical' | (() => 'both' | 'horizontal' | 'vertical');\n /** `AbortSignal` from the component lifecycle. Disposes typeahead timer on abort. */\n signal?: AbortSignal;\n};\n\nexport type ListControl<T> = {\n [Symbol.dispose](): void;\n /** Current focused index. `-1` when nothing is focused. */\n readonly focusedIndex: Readable<number>;\n getActiveItem(): T | undefined;\n handleKeydown(event: KeyboardEvent): boolean;\n /**\n * Programmatically navigate to a named position.\n * Always fires `onNavigate` — consistent with keyboard navigation.\n * Returns the resolved index, or `-1` if no enabled item was found.\n */\n navigate(action: ListNavigationAction): number;\n /** Reset focused index to `-1`. */\n reset(): void;\n /**\n * Set focus to the item at `index`. Returns the resolved index.\n * Pass a negative index to clear focus (same as `reset()`).\n */\n set(index: number): number;\n};\n\nconst hasDisabledProp = (item: unknown): item is { disabled: boolean } =>\n typeof item === 'object' && item !== null && 'disabled' in item;\n\nexport const createListControl = <T>(options: ListNavigationOptions<T>): ListControl<T> => {\n const _index = signal(-1);\n\n const isItemDisabled = (item: T, index: number): boolean =>\n options.isItemDisabled?.(item, index) ?? (hasDisabledProp(item) ? item.disabled : false);\n\n const commitIndex = (idx: number, action: ListKeyAction, event?: KeyboardEvent): number => {\n if (idx >= 0) {\n _index.value = idx;\n options.onNavigate?.(action, idx, event);\n }\n\n return idx;\n };\n\n const findEnabledIndex = (items: T[], start: number, direction: 'forward' | 'backward'): number => {\n if (direction === 'forward') return findForward(items, start, (item, i) => !isItemDisabled(item, i));\n\n return findBackward(items, start, (item, i) => !isItemDisabled(item, i));\n };\n\n const set = (index: number): number => {\n if (index < 0) {\n reset();\n\n return -1;\n }\n\n const items = options.getItems();\n\n if (!items.length) return -1;\n\n const clamped = Math.min(index, items.length - 1);\n\n if (isItemDisabled(items[clamped], clamped)) return -1;\n\n _index.value = clamped;\n\n return clamped;\n };\n\n const move = (direction: 'forward' | 'backward', event?: KeyboardEvent): number => {\n const items = options.getItems();\n const current = _index.value;\n\n if (!items.length) return -1;\n\n const start: number =\n current < 0\n ? direction === 'forward'\n ? 0\n : items.length - 1\n : direction === 'forward'\n ? current + 1\n : current - 1;\n const idx = findEnabledIndex(items, start, direction);\n const action: ListNavigationAction = direction === 'forward' ? 'next' : 'prev';\n\n if (idx >= 0) return commitIndex(idx, action, event);\n\n if (options.loop) {\n const wrapStart = direction === 'forward' ? 0 : items.length - 1;\n const wrapped = findEnabledIndex(items, wrapStart, direction);\n\n if (wrapped >= 0) return commitIndex(wrapped, action, event);\n }\n\n return current;\n };\n\n const navigate = (action: ListNavigationAction): number => {\n const items = options.getItems();\n\n if (!items.length) return -1;\n\n if (action === 'first') {\n const idx = findEnabledIndex(items, 0, 'forward');\n\n return commitIndex(idx, 'first');\n }\n\n if (action === 'last') {\n const idx = findEnabledIndex(items, items.length - 1, 'backward');\n\n return commitIndex(idx, 'last');\n }\n\n return move(action === 'next' ? 'forward' : 'backward');\n };\n\n const getActiveItem = (): T | undefined => {\n const items = options.getItems();\n const index = _index.value;\n\n return index >= 0 && index < items.length ? items[index] : undefined;\n };\n\n const reset = (): void => {\n _index.value = -1;\n };\n\n // ── Keyboard handling ──────────────────────────────────────────────────────\n\n const isKeyDisabled = (): boolean => Boolean(options.disabled?.value);\n\n const resolveOrientation = (): 'both' | 'horizontal' | 'vertical' =>\n typeof options.orientation === 'function' ? options.orientation() : (options.orientation ?? 'vertical');\n\n const buildKeymap = (): Record<string, (keyboardEvent: KeyboardEvent) => void> => {\n const keys = options.keys;\n const orientationDefaults = DEFAULT_KEYS[resolveOrientation()];\n const resolved = {\n first: keys?.first ?? orientationDefaults.first,\n last: keys?.last ?? orientationDefaults.last,\n next: keys?.next ?? orientationDefaults.next,\n prev: keys?.prev ?? orientationDefaults.prev,\n };\n const km: Record<string, (keyboardEvent: KeyboardEvent) => void> = {};\n\n for (const action of ['next', 'prev', 'first', 'last'] as const) {\n for (const key of resolved[action]) {\n km[key] = (keyboardEvent: KeyboardEvent) => {\n if (action === 'first' || action === 'last') {\n const items = options.getItems();\n\n if (!items.length) return;\n\n const idx =\n action === 'first'\n ? findEnabledIndex(items, 0, 'forward')\n : findEnabledIndex(items, items.length - 1, 'backward');\n\n commitIndex(idx, action, keyboardEvent);\n } else {\n move(action === 'next' ? 'forward' : 'backward', keyboardEvent);\n }\n };\n }\n }\n\n return km;\n };\n\n // ── Typeahead ──────────────────────────────────────────────────────────────\n\n const typeahead = options.getItemLabel\n ? createTypeahead({\n getIndex: () => _index.value,\n getItemLabel: options.getItemLabel,\n getItems: options.getItems,\n isItemDisabled: (item, index) => isItemDisabled(item, index),\n onNavigate: (index, event) => {\n if (!isKeyDisabled()) {\n _index.value = index;\n options.onNavigate?.('typeahead', index, event);\n }\n },\n })\n : null;\n\n const _staticKeymap: Record<string, (e: KeyboardEvent) => void> | null =\n typeof options.orientation === 'function' ? null : buildKeymap();\n\n const handleKeydown = (event: KeyboardEvent): boolean => {\n const km = _staticKeymap ?? buildKeymap();\n\n if (dispatchKeyboardAction(event, { disabled: options.disabled, keymap: km })) return true;\n\n return typeahead?.handleKeydown(event) ?? false;\n };\n\n const dispose = (): void => {\n typeahead?.reset();\n };\n\n if (options.signal) {\n options.signal.addEventListener('abort', dispose, { once: true });\n }\n\n return {\n focusedIndex: _index,\n getActiveItem,\n handleKeydown,\n navigate,\n reset,\n set,\n [Symbol.dispose]: dispose,\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"nav.js","names":[],"sources":["../../src/headless/nav.ts"],"sourcesContent":["import { type Readable, signal } from '@vielzeug/ripple';\n\nimport { dispatchKeyboardAction } from './keyboard';\nimport { createTypeahead } from './typeahead';\n\n// ── Private array search helpers ────────────────────────────────────────────\nconst findForward = <T>(items: T[], start: number, predicate: (item: T, index: number) => boolean): number => {\n for (let idx = start; idx < items.length; idx++) {\n if (predicate(items[idx], idx)) return idx;\n }\n\n return -1;\n};\n\nconst findBackward = <T>(items: T[], start: number, predicate: (item: T, index: number) => boolean): number => {\n for (let idx = start; idx >= 0; idx--) {\n if (predicate(items[idx], idx)) return idx;\n }\n\n return -1;\n};\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/**\n * Arrow-key and Home/End navigation actions. Separate from `ListKeyAction` so\n * keymap declarations never need `Exclude<ListKeyAction, 'typeahead'>`.\n */\nexport type ListNavigationAction = 'first' | 'last' | 'next' | 'prev';\n\n/**\n * All possible navigation action names, including `'typeahead'` (character-key\n * search). Used in `onNavigate` callbacks where both nav and typeahead are valid.\n */\nexport type ListKeyAction = ListNavigationAction | 'typeahead';\n\n/**\n * Orientation-aware default key bindings for list navigation.\n * - `'vertical'` (default): Up/Down arrows, Home/End\n * - `'horizontal'`: Left/Right arrows, Home/End\n * - `'both'`: all four arrow keys + Home/End\n */\nconst DEFAULT_KEYS: Record<'both' | 'horizontal' | 'vertical', Record<ListNavigationAction, string[]>> = {\n both: { first: ['Home'], last: ['End'], next: ['ArrowDown', 'ArrowRight'], prev: ['ArrowUp', 'ArrowLeft'] },\n horizontal: { first: ['Home'], last: ['End'], next: ['ArrowRight'], prev: ['ArrowLeft'] },\n vertical: { first: ['Home'], last: ['End'], next: ['ArrowDown'], prev: ['ArrowUp'] },\n};\n\n/**\n * RTL mirror of `DEFAULT_KEYS`'s horizontal component (per WAI-ARIA APG — tabs/radiogroup\n * flip Left/Right in `dir=\"rtl\"` so \"next\" always advances in reading order, not screen-space\n * direction). Only the Left/Right bindings differ; Home/End and the vertical arrows are\n * direction-independent and stay identical to `DEFAULT_KEYS`.\n */\nconst DEFAULT_KEYS_RTL: Record<'both' | 'horizontal' | 'vertical', Record<ListNavigationAction, string[]>> = {\n both: { first: ['Home'], last: ['End'], next: ['ArrowDown', 'ArrowLeft'], prev: ['ArrowUp', 'ArrowRight'] },\n horizontal: { first: ['Home'], last: ['End'], next: ['ArrowLeft'], prev: ['ArrowRight'] },\n vertical: DEFAULT_KEYS.vertical,\n};\n\nexport type ListNavigationOptions<T> = {\n /**\n * Text direction, used to mirror the default Left/Right arrow-key bindings for\n * `'horizontal'`/`'both'` orientation (WAI-ARIA APG: `dir=\"rtl\"` flips which arrow key\n * means \"next\" vs \"prev\"). Pass a getter for dynamic/reactive direction (e.g. derived from\n * `elementDirection(getHost())`, since a `dir` ancestor attribute can change at runtime).\n * Has no effect when an explicit `keys` override is provided. Defaults to `'ltr'`.\n */\n direction?: 'ltr' | 'rtl' | (() => 'ltr' | 'rtl');\n disabled?: Readable<boolean | undefined>;\n /**\n * Returns a plain-text label for an item used by typeahead search.\n * When provided, pressing a printable character key jumps to the first enabled\n * item whose label starts with the accumulated type buffer (500 ms reset window).\n * Omit for contexts where the host element is a text input (e.g. combobox).\n */\n getItemLabel?: (item: T, index: number) => string;\n getItems: () => T[];\n isItemDisabled?: (item: T, index: number) => boolean;\n /** Override default key bindings for navigation actions. */\n keys?: Partial<Record<ListNavigationAction, string[]>>;\n loop?: boolean;\n /**\n * Called when navigation lands on a new index.\n * `event` is the originating `KeyboardEvent`, or `undefined` for programmatic navigation.\n * Use to sync side-effects such as focusing a DOM element.\n */\n onNavigate?: (action: ListKeyAction, index: number, event?: KeyboardEvent) => void;\n /**\n * Axis the list navigates along. Controls which arrow keys are treated as\n * `next`/`prev` when no explicit `keys` override is provided.\n * Pass a getter function for dynamic/reactive orientation (e.g. driven by a prop signal).\n *\n * - `'vertical'` (default): ↑↓ arrows\n * - `'horizontal'`: ←→ arrows\n * - `'both'`: all four arrow keys\n */\n orientation?: 'both' | 'horizontal' | 'vertical' | (() => 'both' | 'horizontal' | 'vertical');\n /** `AbortSignal` from the component lifecycle. Disposes typeahead timer on abort. */\n signal?: AbortSignal;\n};\n\nexport type ListControl<T> = {\n [Symbol.dispose](): void;\n /** Disposes the internal typeahead timer. Alias for `dispose()`. Conforms to the monorepo disposal convention. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** Current focused index. `-1` when nothing is focused. */\n readonly focusedIndex: Readable<number>;\n getActiveItem(): T | undefined;\n handleKeydown(event: KeyboardEvent): boolean;\n /**\n * Programmatically navigate to a named position.\n * Always fires `onNavigate` — consistent with keyboard navigation.\n * Returns the resolved index, or `-1` if no enabled item was found.\n */\n navigate(action: ListNavigationAction): number;\n /** Reset focused index to `-1`. */\n reset(): void;\n /**\n * Set focus to the item at `index`. Returns the resolved index.\n * Pass a negative index to clear focus (same as `reset()`).\n */\n set(index: number): number;\n};\n\nconst hasDisabledProp = (item: unknown): item is { disabled: boolean } =>\n typeof item === 'object' && item !== null && 'disabled' in item;\n\nexport const createListControl = <T>(options: ListNavigationOptions<T>): ListControl<T> => {\n const _index = signal(-1);\n\n const isItemDisabled = (item: T, index: number): boolean =>\n options.isItemDisabled?.(item, index) ?? (hasDisabledProp(item) ? item.disabled : false);\n\n const commitIndex = (idx: number, action: ListKeyAction, event?: KeyboardEvent): number => {\n if (idx >= 0) {\n _index.value = idx;\n options.onNavigate?.(action, idx, event);\n }\n\n return idx;\n };\n\n const findEnabledIndex = (items: T[], start: number, direction: 'forward' | 'backward'): number => {\n if (direction === 'forward') return findForward(items, start, (item, i) => !isItemDisabled(item, i));\n\n return findBackward(items, start, (item, i) => !isItemDisabled(item, i));\n };\n\n const set = (index: number): number => {\n if (index < 0) {\n reset();\n\n return -1;\n }\n\n const items = options.getItems();\n\n if (!items.length) return -1;\n\n const clamped = Math.min(index, items.length - 1);\n\n if (isItemDisabled(items[clamped], clamped)) return -1;\n\n _index.value = clamped;\n\n return clamped;\n };\n\n const move = (direction: 'forward' | 'backward', event?: KeyboardEvent): number => {\n const items = options.getItems();\n const current = _index.value;\n\n if (!items.length) return -1;\n\n const start: number =\n current < 0\n ? direction === 'forward'\n ? 0\n : items.length - 1\n : direction === 'forward'\n ? current + 1\n : current - 1;\n const idx = findEnabledIndex(items, start, direction);\n const action: ListNavigationAction = direction === 'forward' ? 'next' : 'prev';\n\n if (idx >= 0) return commitIndex(idx, action, event);\n\n if (options.loop) {\n const wrapStart = direction === 'forward' ? 0 : items.length - 1;\n const wrapped = findEnabledIndex(items, wrapStart, direction);\n\n if (wrapped >= 0) return commitIndex(wrapped, action, event);\n }\n\n return current;\n };\n\n const navigate = (action: ListNavigationAction): number => {\n const items = options.getItems();\n\n if (!items.length) return -1;\n\n if (action === 'first') {\n const idx = findEnabledIndex(items, 0, 'forward');\n\n return commitIndex(idx, 'first');\n }\n\n if (action === 'last') {\n const idx = findEnabledIndex(items, items.length - 1, 'backward');\n\n return commitIndex(idx, 'last');\n }\n\n return move(action === 'next' ? 'forward' : 'backward');\n };\n\n const getActiveItem = (): T | undefined => {\n const items = options.getItems();\n const index = _index.value;\n\n return index >= 0 && index < items.length ? items[index] : undefined;\n };\n\n const reset = (): void => {\n _index.value = -1;\n };\n\n // ── Keyboard handling ──────────────────────────────────────────────────────\n\n const isKeyDisabled = (): boolean => Boolean(options.disabled?.value);\n\n const resolveOrientation = (): 'both' | 'horizontal' | 'vertical' =>\n typeof options.orientation === 'function' ? options.orientation() : (options.orientation ?? 'vertical');\n\n const resolveDirection = (): 'ltr' | 'rtl' =>\n typeof options.direction === 'function' ? options.direction() : (options.direction ?? 'ltr');\n\n const buildKeymap = (): Record<string, (keyboardEvent: KeyboardEvent) => void> => {\n const keys = options.keys;\n const keyTable = resolveDirection() === 'rtl' ? DEFAULT_KEYS_RTL : DEFAULT_KEYS;\n const orientationDefaults = keyTable[resolveOrientation()];\n const resolved = {\n first: keys?.first ?? orientationDefaults.first,\n last: keys?.last ?? orientationDefaults.last,\n next: keys?.next ?? orientationDefaults.next,\n prev: keys?.prev ?? orientationDefaults.prev,\n };\n const km: Record<string, (keyboardEvent: KeyboardEvent) => void> = {};\n\n for (const action of ['next', 'prev', 'first', 'last'] as const) {\n for (const key of resolved[action]) {\n km[key] = (keyboardEvent: KeyboardEvent) => {\n if (action === 'first' || action === 'last') {\n const items = options.getItems();\n\n if (!items.length) return;\n\n const idx =\n action === 'first'\n ? findEnabledIndex(items, 0, 'forward')\n : findEnabledIndex(items, items.length - 1, 'backward');\n\n commitIndex(idx, action, keyboardEvent);\n } else {\n move(action === 'next' ? 'forward' : 'backward', keyboardEvent);\n }\n };\n }\n }\n\n return km;\n };\n\n // ── Typeahead ──────────────────────────────────────────────────────────────\n\n const typeahead = options.getItemLabel\n ? createTypeahead({\n getIndex: () => _index.value,\n getItemLabel: options.getItemLabel,\n getItems: options.getItems,\n isItemDisabled: (item, index) => isItemDisabled(item, index),\n onNavigate: (index, event) => {\n if (!isKeyDisabled()) {\n _index.value = index;\n options.onNavigate?.('typeahead', index, event);\n }\n },\n })\n : null;\n\n // Keymap depends on both orientation and direction — if either is dynamic, rebuild on every\n // keydown instead of caching once.\n const isDynamicKeymap = typeof options.orientation === 'function' || typeof options.direction === 'function';\n const _staticKeymap: Record<string, (e: KeyboardEvent) => void> | null = isDynamicKeymap ? null : buildKeymap();\n\n const handleKeydown = (event: KeyboardEvent): boolean => {\n const km = _staticKeymap ?? buildKeymap();\n\n if (dispatchKeyboardAction(event, { disabled: options.disabled, keymap: km })) return true;\n\n return typeahead?.handleKeydown(event) ?? false;\n };\n\n let disposed = false;\n\n const dispose = (): void => {\n if (disposed) return;\n\n disposed = true;\n typeahead?.reset();\n };\n\n if (options.signal) {\n options.signal.addEventListener('abort', dispose, { once: true });\n }\n\n return {\n dispose,\n get disposed() {\n return disposed;\n },\n focusedIndex: _index,\n getActiveItem,\n handleKeydown,\n navigate,\n reset,\n set,\n [Symbol.dispose]: dispose,\n };\n};\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e=e=>{let t=null,n=()=>typeof e.restoreFocus==`function`?e.restoreFocus():e.restoreFocus??!0,r=t=>{if(!e.isOpen())return;let n=t.composedPath?.()??[],r=e.getBoundary(),i=e.getPanel?.()??null,a=n[0]??t.target,s=a instanceof Node?a:null,c=n.some(e=>e===r||e===i),l=s?(r?.contains(s)??!1)||(i?.contains(s)??!1):!1;!c&&!l&&o(`outsideClick`)},i=e=>{e?document.addEventListener(`pointerdown`,r,{capture:!0}):document.removeEventListener(`pointerdown`,r,{capture:!0})},a=(n=`programmatic`)=>{e.isDisabled?.()||e.isOpen()||(e.setOpen(!0,n),i(!0),e.positioner&&(e.positioner.update(),t=e.positioner.startAutoUpdate?.()??null),e.onOpen?.(n))},o=(r=`programmatic`,a,o=!1)=>{e.isOpen()&&(e.setOpen(!1,r),i(!1),t&&=(t(),null),(a??n())&&e.getTrigger?.()?.focus(),o||e.onClose?.(r))},s=(t=`click`,n=`trigger`)=>{e.isOpen()?o(n):a(t)},c=()=>{e.isOpen()&&o(`programmatic`,!1,!0),i(!1),t&&=(t(),null)};return e.signal.addEventListener(`abort`,
|
|
1
|
+
var e=e=>{let t=null,n=()=>typeof e.restoreFocus==`function`?e.restoreFocus():e.restoreFocus??!0,r=t=>{if(!e.isOpen())return;let n=t.composedPath?.()??[],r=e.getBoundary(),i=e.getPanel?.()??null,a=n[0]??t.target,s=a instanceof Node?a:null,c=n.some(e=>e===r||e===i),l=s?(r?.contains(s)??!1)||(i?.contains(s)??!1):!1;!c&&!l&&o(`outsideClick`)},i=e=>{e?document.addEventListener(`pointerdown`,r,{capture:!0}):document.removeEventListener(`pointerdown`,r,{capture:!0})},a=(n=`programmatic`)=>{e.isDisabled?.()||e.isOpen()||(e.setOpen(!0,n),i(!0),e.positioner&&(e.positioner.update(),t=e.positioner.startAutoUpdate?.()??null),e.onOpen?.(n))},o=(r=`programmatic`,a,o=!1)=>{e.isOpen()&&(e.setOpen(!1,r),i(!1),t&&=(t(),null),(a??n())&&e.getTrigger?.()?.focus(),o||e.onClose?.(r))},s=(t=`click`,n=`trigger`)=>{e.isOpen()?o(n):a(t)},c=!1,l=()=>{c||(c=!0,e.isOpen()&&o(`programmatic`,!1,!0),i(!1),t&&=(t(),null))};return e.signal.addEventListener(`abort`,l,{once:!0}),{close:o,dispose:l,get disposed(){return c},open:a,[Symbol.dispose]:l,toggle:s}};exports.createOverlayControl=e;
|
|
2
2
|
//# sourceMappingURL=overlay.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlay.cjs","names":[],"sources":["../../src/headless/overlay.ts"],"sourcesContent":["import type { OverlayPositioner } from './positioner';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/**\n * Why an overlay was opened.\n * - `'click'` — user clicked the trigger element\n * - `'focus'` — focus entered the trigger (tooltip, popover)\n * - `'hover'` — pointer entered the trigger (tooltip)\n * - `'keyboard'` — keyboard shortcut or Enter/Space on a trigger\n * - `'programmatic'` — opened via the JS API without a user gesture\n * - `'trigger'` — opened via the Invoker Commands API (`command=\"show-modal\"`)\n */\nexport type OverlayOpenReason = 'click' | 'focus' | 'hover' | 'keyboard' | 'programmatic' | 'trigger';\n\n/** Close reasons valid for modal dialogs (includes 'swipe' for drawer). */\nexport type DialogCloseReason = 'escape' | 'outsideClick' | 'programmatic' | 'swipe' | 'trigger';\n/** Close reasons valid for dropdown overlays (no swipe). */\nexport type DropdownCloseReason = Exclude<DialogCloseReason, 'swipe'>;\n\n/** Detail shape for overlay open events emitted by components. */\nexport type OverlayOpenDetail = { reason: OverlayOpenReason };\n/** Detail shape for overlay close events emitted by components. */\nexport type OverlayCloseDetail = { reason: DialogCloseReason };\n\nexport type { OverlayPositioner };\n\nexport type OverlayControlOptions = {\n getBoundary: () => HTMLElement | null;\n getPanel?: () => HTMLElement | null;\n getTrigger?: () => HTMLElement | null;\n isDisabled?: () => boolean;\n isOpen: () => boolean;\n onClose?: (reason: DialogCloseReason) => void;\n onOpen?: (reason: OverlayOpenReason) => void;\n positioner?: OverlayPositioner;\n restoreFocus?: boolean | (() => boolean);\n setOpen: (next: boolean, reason: OverlayOpenReason | DialogCloseReason) => void;\n /** `AbortSignal` from the component lifecycle. `dispose()` is called automatically on abort. */\n signal: AbortSignal;\n};\n\nexport type OverlayControl = {\n [Symbol.dispose](): void;\n /**\n * Closes the overlay.\n * @param reason — why it's closing (default `'programmatic'`)\n * @param restoreFocus — override per-call focus restoration (default: uses `restoreFocus` option)\n */\n close(reason?: DialogCloseReason, restoreFocus?: boolean): void;\n /** Tears down the overlay: closes silently and removes all event listeners. */\n dispose(): void;\n /**\n * Opens the overlay.\n * @param reason — why it's opening (default `'programmatic'`)\n */\n open(reason?: OverlayOpenReason): void;\n /**\n * Toggles open state.\n * - Opens with `openReason` (default `'click'`).\n * - Closes with `closeReason` (default `'trigger'`).\n */\n toggle(openReason?: OverlayOpenReason, closeReason?: DialogCloseReason): void;\n};\n\n// ── Factory ───────────────────────────────────────────────────────────────────\nexport const createOverlayControl = (options: OverlayControlOptions): OverlayControl => {\n let positionerCleanup: (() => void) | null = null;\n\n const shouldRestoreFocus = (): boolean => {\n if (typeof options.restoreFocus === 'function') return options.restoreFocus();\n\n return options.restoreFocus ?? true;\n };\n\n // Per-instance outside-pointer listener registered directly on document.\n // Uses 'pointerdown' instead of 'click' so that tapping empty/non-interactive\n // areas on iOS Safari (which does not synthesize 'click' for those targets)\n // still dismisses the overlay.\n // No shared multiplexer needed — max ~3 overlays can be open concurrently.\n const clickListener = (event: Event): void => {\n if (!options.isOpen()) return;\n\n const path = (event as Event & { composedPath?: () => EventTarget[] }).composedPath?.() ?? [];\n const boundary = options.getBoundary();\n const panel = options.getPanel?.() ?? null;\n const target = (path[0] ?? event.target) as EventTarget | null;\n const nodeTarget = target instanceof Node ? target : null;\n const insideByPath = path.some((entry) => entry === boundary || entry === panel);\n const insideByContainment = nodeTarget\n ? (boundary?.contains(nodeTarget) ?? false) || (panel?.contains(nodeTarget) ?? false)\n : false;\n\n if (!insideByPath && !insideByContainment) close('outsideClick');\n };\n\n const registerClickListener = (active: boolean): void => {\n if (active) {\n document.addEventListener('pointerdown', clickListener, { capture: true });\n } else {\n document.removeEventListener('pointerdown', clickListener, { capture: true });\n }\n };\n\n const open = (reason: OverlayOpenReason = 'programmatic'): void => {\n if (options.isDisabled?.() || options.isOpen()) return;\n\n options.setOpen(true, reason);\n registerClickListener(true);\n\n if (options.positioner) {\n options.positioner.update();\n positionerCleanup = options.positioner.startAutoUpdate?.() ?? null;\n }\n\n options.onOpen?.(reason);\n };\n\n const close = (reason: DialogCloseReason = 'programmatic', restoreFocus?: boolean, silent = false): void => {\n if (!options.isOpen()) return;\n\n options.setOpen(false, reason);\n registerClickListener(false);\n\n if (positionerCleanup) {\n positionerCleanup();\n positionerCleanup = null;\n }\n\n const restore = restoreFocus ?? shouldRestoreFocus();\n\n if (restore) options.getTrigger?.()?.focus();\n\n if (!silent) options.onClose?.(reason);\n };\n\n const toggle = (openReason: OverlayOpenReason = 'click', closeReason: DialogCloseReason = 'trigger'): void => {\n if (options.isOpen()) {\n close(closeReason);\n } else {\n open(openReason);\n }\n };\n\n const dispose = (): void => {\n // Close silently — teardown should not fire onClose callbacks.\n if (options.isOpen()) close('programmatic', false, true);\n\n registerClickListener(false);\n\n if (positionerCleanup) {\n positionerCleanup();\n positionerCleanup = null;\n }\n };\n\n options.signal.addEventListener('abort', dispose, { once: true });\n\n return {
|
|
1
|
+
{"version":3,"file":"overlay.cjs","names":[],"sources":["../../src/headless/overlay.ts"],"sourcesContent":["import type { OverlayPositioner } from './positioner';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/**\n * Why an overlay was opened.\n * - `'click'` — user clicked the trigger element\n * - `'focus'` — focus entered the trigger (tooltip, popover)\n * - `'hover'` — pointer entered the trigger (tooltip)\n * - `'keyboard'` — keyboard shortcut or Enter/Space on a trigger\n * - `'programmatic'` — opened via the JS API without a user gesture\n * - `'trigger'` — opened via the Invoker Commands API (`command=\"show-modal\"`)\n */\nexport type OverlayOpenReason = 'click' | 'focus' | 'hover' | 'keyboard' | 'programmatic' | 'trigger';\n\n/** Close reasons valid for modal dialogs (includes 'swipe' for drawer). */\nexport type DialogCloseReason = 'escape' | 'outsideClick' | 'programmatic' | 'swipe' | 'trigger';\n/** Close reasons valid for dropdown overlays (no swipe). */\nexport type DropdownCloseReason = Exclude<DialogCloseReason, 'swipe'>;\n\n/** Detail shape for overlay open events emitted by components. */\nexport type OverlayOpenDetail = { reason: OverlayOpenReason };\n/** Detail shape for overlay close events emitted by components. */\nexport type OverlayCloseDetail = { reason: DialogCloseReason };\n\nexport type { OverlayPositioner };\n\nexport type OverlayControlOptions = {\n getBoundary: () => HTMLElement | null;\n getPanel?: () => HTMLElement | null;\n getTrigger?: () => HTMLElement | null;\n isDisabled?: () => boolean;\n isOpen: () => boolean;\n onClose?: (reason: DialogCloseReason) => void;\n onOpen?: (reason: OverlayOpenReason) => void;\n positioner?: OverlayPositioner;\n restoreFocus?: boolean | (() => boolean);\n setOpen: (next: boolean, reason: OverlayOpenReason | DialogCloseReason) => void;\n /** `AbortSignal` from the component lifecycle. `dispose()` is called automatically on abort. */\n signal: AbortSignal;\n};\n\nexport type OverlayControl = {\n [Symbol.dispose](): void;\n /**\n * Closes the overlay.\n * @param reason — why it's closing (default `'programmatic'`)\n * @param restoreFocus — override per-call focus restoration (default: uses `restoreFocus` option)\n */\n close(reason?: DialogCloseReason, restoreFocus?: boolean): void;\n /** Tears down the overlay: closes silently and removes all event listeners. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /**\n * Opens the overlay.\n * @param reason — why it's opening (default `'programmatic'`)\n */\n open(reason?: OverlayOpenReason): void;\n /**\n * Toggles open state.\n * - Opens with `openReason` (default `'click'`).\n * - Closes with `closeReason` (default `'trigger'`).\n */\n toggle(openReason?: OverlayOpenReason, closeReason?: DialogCloseReason): void;\n};\n\n// ── Factory ───────────────────────────────────────────────────────────────────\nexport const createOverlayControl = (options: OverlayControlOptions): OverlayControl => {\n let positionerCleanup: (() => void) | null = null;\n\n const shouldRestoreFocus = (): boolean => {\n if (typeof options.restoreFocus === 'function') return options.restoreFocus();\n\n return options.restoreFocus ?? true;\n };\n\n // Per-instance outside-pointer listener registered directly on document.\n // Uses 'pointerdown' instead of 'click' so that tapping empty/non-interactive\n // areas on iOS Safari (which does not synthesize 'click' for those targets)\n // still dismisses the overlay.\n // No shared multiplexer needed — max ~3 overlays can be open concurrently.\n const clickListener = (event: Event): void => {\n if (!options.isOpen()) return;\n\n const path = (event as Event & { composedPath?: () => EventTarget[] }).composedPath?.() ?? [];\n const boundary = options.getBoundary();\n const panel = options.getPanel?.() ?? null;\n const target = (path[0] ?? event.target) as EventTarget | null;\n const nodeTarget = target instanceof Node ? target : null;\n const insideByPath = path.some((entry) => entry === boundary || entry === panel);\n const insideByContainment = nodeTarget\n ? (boundary?.contains(nodeTarget) ?? false) || (panel?.contains(nodeTarget) ?? false)\n : false;\n\n if (!insideByPath && !insideByContainment) close('outsideClick');\n };\n\n const registerClickListener = (active: boolean): void => {\n if (active) {\n document.addEventListener('pointerdown', clickListener, { capture: true });\n } else {\n document.removeEventListener('pointerdown', clickListener, { capture: true });\n }\n };\n\n const open = (reason: OverlayOpenReason = 'programmatic'): void => {\n if (options.isDisabled?.() || options.isOpen()) return;\n\n options.setOpen(true, reason);\n registerClickListener(true);\n\n if (options.positioner) {\n options.positioner.update();\n positionerCleanup = options.positioner.startAutoUpdate?.() ?? null;\n }\n\n options.onOpen?.(reason);\n };\n\n const close = (reason: DialogCloseReason = 'programmatic', restoreFocus?: boolean, silent = false): void => {\n if (!options.isOpen()) return;\n\n options.setOpen(false, reason);\n registerClickListener(false);\n\n if (positionerCleanup) {\n positionerCleanup();\n positionerCleanup = null;\n }\n\n const restore = restoreFocus ?? shouldRestoreFocus();\n\n if (restore) options.getTrigger?.()?.focus();\n\n if (!silent) options.onClose?.(reason);\n };\n\n const toggle = (openReason: OverlayOpenReason = 'click', closeReason: DialogCloseReason = 'trigger'): void => {\n if (options.isOpen()) {\n close(closeReason);\n } else {\n open(openReason);\n }\n };\n\n let disposed = false;\n\n const dispose = (): void => {\n if (disposed) return;\n\n disposed = true;\n\n // Close silently — teardown should not fire onClose callbacks.\n if (options.isOpen()) close('programmatic', false, true);\n\n registerClickListener(false);\n\n if (positionerCleanup) {\n positionerCleanup();\n positionerCleanup = null;\n }\n };\n\n options.signal.addEventListener('abort', dispose, { once: true });\n\n return {\n close,\n dispose,\n get disposed() {\n return disposed;\n },\n open,\n [Symbol.dispose]: dispose,\n toggle,\n };\n};\n"],"mappings":"AAoEA,IAAa,EAAwB,GAAmD,CACtF,IAAI,EAAyC,KAEvC,MACA,OAAO,EAAQ,cAAiB,WAAmB,EAAQ,aAAa,EAErE,EAAQ,cAAgB,GAQ3B,EAAiB,GAAuB,CAC5C,GAAI,CAAC,EAAQ,OAAO,EAAG,OAEvB,IAAM,EAAQ,EAAyD,eAAe,GAAK,CAAC,EACtF,EAAW,EAAQ,YAAY,EAC/B,EAAQ,EAAQ,WAAW,GAAK,KAChC,EAAU,EAAK,IAAM,EAAM,OAC3B,EAAa,aAAkB,KAAO,EAAS,KAC/C,EAAe,EAAK,KAAM,GAAU,IAAU,GAAY,IAAU,CAAK,EACzE,EAAsB,GACvB,GAAU,SAAS,CAAU,GAAK,MAAW,GAAO,SAAS,CAAU,GAAK,IAC7E,GAEA,CAAC,GAAgB,CAAC,GAAqB,EAAM,cAAc,CACjE,EAEM,EAAyB,GAA0B,CACnD,EACF,SAAS,iBAAiB,cAAe,EAAe,CAAE,QAAS,EAAK,CAAC,EAEzE,SAAS,oBAAoB,cAAe,EAAe,CAAE,QAAS,EAAK,CAAC,CAEhF,EAEM,GAAQ,EAA4B,iBAAyB,CAC7D,EAAQ,aAAa,GAAK,EAAQ,OAAO,IAE7C,EAAQ,QAAQ,GAAM,CAAM,EAC5B,EAAsB,EAAI,EAEtB,EAAQ,aACV,EAAQ,WAAW,OAAO,EAC1B,EAAoB,EAAQ,WAAW,kBAAkB,GAAK,MAGhE,EAAQ,SAAS,CAAM,EACzB,EAEM,GAAS,EAA4B,eAAgB,EAAwB,EAAS,KAAgB,CACrG,EAAQ,OAAO,IAEpB,EAAQ,QAAQ,GAAO,CAAM,EAC7B,EAAsB,EAAK,EAE3B,AAEE,KADA,EAAkB,EACE,OAGN,GAAgB,EAAmB,IAEtC,EAAQ,aAAa,CAAC,EAAE,MAAM,EAEtC,GAAQ,EAAQ,UAAU,CAAM,EACvC,EAEM,GAAU,EAAgC,QAAS,EAAiC,YAAoB,CACxG,EAAQ,OAAO,EACjB,EAAM,CAAW,EAEjB,EAAK,CAAU,CAEnB,EAEI,EAAW,GAET,MAAsB,CACtB,IAEJ,EAAW,GAGP,EAAQ,OAAO,GAAG,EAAM,eAAgB,GAAO,EAAI,EAEvD,EAAsB,EAAK,EAE3B,AAEE,KADA,EAAkB,EACE,MAExB,EAIA,OAFA,EAAQ,OAAO,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,EAEzD,CACL,QACA,UACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,QACC,OAAO,SAAU,EAClB,QACF,CACF"}
|
|
@@ -46,6 +46,8 @@ export type OverlayControl = {
|
|
|
46
46
|
close(reason?: DialogCloseReason, restoreFocus?: boolean): void;
|
|
47
47
|
/** Tears down the overlay: closes silently and removes all event listeners. */
|
|
48
48
|
dispose(): void;
|
|
49
|
+
/** `true` after `dispose()` has been called. */
|
|
50
|
+
readonly disposed: boolean;
|
|
49
51
|
/**
|
|
50
52
|
* Opens the overlay.
|
|
51
53
|
* @param reason — why it's opening (default `'programmatic'`)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/headless/overlay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAItD;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,cAAc,GAAG,SAAS,CAAC;AAEtG,2EAA2E;AAC3E,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,cAAc,GAAG,cAAc,GAAG,OAAO,GAAG,SAAS,CAAC;AACjG,4DAA4D;AAC5D,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC;AAEtE,kEAAkE;AAClE,MAAM,MAAM,iBAAiB,GAAG;IAAE,MAAM,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAC9D,mEAAmE;AACnE,MAAM,MAAM,kBAAkB,GAAG;IAAE,MAAM,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAE/D,YAAY,EAAE,iBAAiB,EAAE,CAAC;AAElC,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;IAC3B,MAAM,EAAE,MAAM,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC9C,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC7C,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,YAAY,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,CAAC;IACzC,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,iBAAiB,GAAG,iBAAiB,KAAK,IAAI,CAAC;IAChF,gGAAgG;IAChG,MAAM,EAAE,WAAW,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;IACzB;;;;OAIG;IACH,KAAK,CAAC,MAAM,CAAC,EAAE,iBAAiB,EAAE,YAAY,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAChE,+EAA+E;IAC/E,OAAO,IAAI,IAAI,CAAC;IAChB;;;OAGG;IACH,IAAI,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACvC;;;;OAIG;IACH,MAAM,CAAC,UAAU,CAAC,EAAE,iBAAiB,EAAE,WAAW,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAC/E,CAAC;AAGF,eAAO,MAAM,oBAAoB,GAAI,SAAS,qBAAqB,KAAG,
|
|
1
|
+
{"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/headless/overlay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAItD;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,cAAc,GAAG,SAAS,CAAC;AAEtG,2EAA2E;AAC3E,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,cAAc,GAAG,cAAc,GAAG,OAAO,GAAG,SAAS,CAAC;AACjG,4DAA4D;AAC5D,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC;AAEtE,kEAAkE;AAClE,MAAM,MAAM,iBAAiB,GAAG;IAAE,MAAM,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAC9D,mEAAmE;AACnE,MAAM,MAAM,kBAAkB,GAAG;IAAE,MAAM,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAE/D,YAAY,EAAE,iBAAiB,EAAE,CAAC;AAElC,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;IAC3B,MAAM,EAAE,MAAM,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC9C,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC7C,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,YAAY,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,CAAC;IACzC,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,iBAAiB,GAAG,iBAAiB,KAAK,IAAI,CAAC;IAChF,gGAAgG;IAChG,MAAM,EAAE,WAAW,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;IACzB;;;;OAIG;IACH,KAAK,CAAC,MAAM,CAAC,EAAE,iBAAiB,EAAE,YAAY,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAChE,+EAA+E;IAC/E,OAAO,IAAI,IAAI,CAAC;IAChB,gDAAgD;IAChD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,IAAI,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACvC;;;;OAIG;IACH,MAAM,CAAC,UAAU,CAAC,EAAE,iBAAiB,EAAE,WAAW,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAC/E,CAAC;AAGF,eAAO,MAAM,oBAAoB,GAAI,SAAS,qBAAqB,KAAG,cA4GrE,CAAC"}
|
package/dist/headless/overlay.js
CHANGED
|
@@ -12,14 +12,17 @@ var e = (e) => {
|
|
|
12
12
|
e.isOpen() && (e.setOpen(!1, r), i(!1), t &&= (t(), null), (a ?? n()) && e.getTrigger?.()?.focus(), o || e.onClose?.(r));
|
|
13
13
|
}, s = (t = "click", n = "trigger") => {
|
|
14
14
|
e.isOpen() ? o(n) : a(t);
|
|
15
|
-
}, c = () => {
|
|
16
|
-
e.isOpen() && o("programmatic", !1, !0), i(!1), t &&= (t(), null);
|
|
15
|
+
}, c = !1, l = () => {
|
|
16
|
+
c || (c = !0, e.isOpen() && o("programmatic", !1, !0), i(!1), t &&= (t(), null));
|
|
17
17
|
};
|
|
18
|
-
return e.signal.addEventListener("abort",
|
|
18
|
+
return e.signal.addEventListener("abort", l, { once: !0 }), {
|
|
19
19
|
close: o,
|
|
20
|
-
dispose:
|
|
20
|
+
dispose: l,
|
|
21
|
+
get disposed() {
|
|
22
|
+
return c;
|
|
23
|
+
},
|
|
21
24
|
open: a,
|
|
22
|
-
[Symbol.dispose]:
|
|
25
|
+
[Symbol.dispose]: l,
|
|
23
26
|
toggle: s
|
|
24
27
|
};
|
|
25
28
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlay.js","names":[],"sources":["../../src/headless/overlay.ts"],"sourcesContent":["import type { OverlayPositioner } from './positioner';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/**\n * Why an overlay was opened.\n * - `'click'` — user clicked the trigger element\n * - `'focus'` — focus entered the trigger (tooltip, popover)\n * - `'hover'` — pointer entered the trigger (tooltip)\n * - `'keyboard'` — keyboard shortcut or Enter/Space on a trigger\n * - `'programmatic'` — opened via the JS API without a user gesture\n * - `'trigger'` — opened via the Invoker Commands API (`command=\"show-modal\"`)\n */\nexport type OverlayOpenReason = 'click' | 'focus' | 'hover' | 'keyboard' | 'programmatic' | 'trigger';\n\n/** Close reasons valid for modal dialogs (includes 'swipe' for drawer). */\nexport type DialogCloseReason = 'escape' | 'outsideClick' | 'programmatic' | 'swipe' | 'trigger';\n/** Close reasons valid for dropdown overlays (no swipe). */\nexport type DropdownCloseReason = Exclude<DialogCloseReason, 'swipe'>;\n\n/** Detail shape for overlay open events emitted by components. */\nexport type OverlayOpenDetail = { reason: OverlayOpenReason };\n/** Detail shape for overlay close events emitted by components. */\nexport type OverlayCloseDetail = { reason: DialogCloseReason };\n\nexport type { OverlayPositioner };\n\nexport type OverlayControlOptions = {\n getBoundary: () => HTMLElement | null;\n getPanel?: () => HTMLElement | null;\n getTrigger?: () => HTMLElement | null;\n isDisabled?: () => boolean;\n isOpen: () => boolean;\n onClose?: (reason: DialogCloseReason) => void;\n onOpen?: (reason: OverlayOpenReason) => void;\n positioner?: OverlayPositioner;\n restoreFocus?: boolean | (() => boolean);\n setOpen: (next: boolean, reason: OverlayOpenReason | DialogCloseReason) => void;\n /** `AbortSignal` from the component lifecycle. `dispose()` is called automatically on abort. */\n signal: AbortSignal;\n};\n\nexport type OverlayControl = {\n [Symbol.dispose](): void;\n /**\n * Closes the overlay.\n * @param reason — why it's closing (default `'programmatic'`)\n * @param restoreFocus — override per-call focus restoration (default: uses `restoreFocus` option)\n */\n close(reason?: DialogCloseReason, restoreFocus?: boolean): void;\n /** Tears down the overlay: closes silently and removes all event listeners. */\n dispose(): void;\n /**\n * Opens the overlay.\n * @param reason — why it's opening (default `'programmatic'`)\n */\n open(reason?: OverlayOpenReason): void;\n /**\n * Toggles open state.\n * - Opens with `openReason` (default `'click'`).\n * - Closes with `closeReason` (default `'trigger'`).\n */\n toggle(openReason?: OverlayOpenReason, closeReason?: DialogCloseReason): void;\n};\n\n// ── Factory ───────────────────────────────────────────────────────────────────\nexport const createOverlayControl = (options: OverlayControlOptions): OverlayControl => {\n let positionerCleanup: (() => void) | null = null;\n\n const shouldRestoreFocus = (): boolean => {\n if (typeof options.restoreFocus === 'function') return options.restoreFocus();\n\n return options.restoreFocus ?? true;\n };\n\n // Per-instance outside-pointer listener registered directly on document.\n // Uses 'pointerdown' instead of 'click' so that tapping empty/non-interactive\n // areas on iOS Safari (which does not synthesize 'click' for those targets)\n // still dismisses the overlay.\n // No shared multiplexer needed — max ~3 overlays can be open concurrently.\n const clickListener = (event: Event): void => {\n if (!options.isOpen()) return;\n\n const path = (event as Event & { composedPath?: () => EventTarget[] }).composedPath?.() ?? [];\n const boundary = options.getBoundary();\n const panel = options.getPanel?.() ?? null;\n const target = (path[0] ?? event.target) as EventTarget | null;\n const nodeTarget = target instanceof Node ? target : null;\n const insideByPath = path.some((entry) => entry === boundary || entry === panel);\n const insideByContainment = nodeTarget\n ? (boundary?.contains(nodeTarget) ?? false) || (panel?.contains(nodeTarget) ?? false)\n : false;\n\n if (!insideByPath && !insideByContainment) close('outsideClick');\n };\n\n const registerClickListener = (active: boolean): void => {\n if (active) {\n document.addEventListener('pointerdown', clickListener, { capture: true });\n } else {\n document.removeEventListener('pointerdown', clickListener, { capture: true });\n }\n };\n\n const open = (reason: OverlayOpenReason = 'programmatic'): void => {\n if (options.isDisabled?.() || options.isOpen()) return;\n\n options.setOpen(true, reason);\n registerClickListener(true);\n\n if (options.positioner) {\n options.positioner.update();\n positionerCleanup = options.positioner.startAutoUpdate?.() ?? null;\n }\n\n options.onOpen?.(reason);\n };\n\n const close = (reason: DialogCloseReason = 'programmatic', restoreFocus?: boolean, silent = false): void => {\n if (!options.isOpen()) return;\n\n options.setOpen(false, reason);\n registerClickListener(false);\n\n if (positionerCleanup) {\n positionerCleanup();\n positionerCleanup = null;\n }\n\n const restore = restoreFocus ?? shouldRestoreFocus();\n\n if (restore) options.getTrigger?.()?.focus();\n\n if (!silent) options.onClose?.(reason);\n };\n\n const toggle = (openReason: OverlayOpenReason = 'click', closeReason: DialogCloseReason = 'trigger'): void => {\n if (options.isOpen()) {\n close(closeReason);\n } else {\n open(openReason);\n }\n };\n\n const dispose = (): void => {\n // Close silently — teardown should not fire onClose callbacks.\n if (options.isOpen()) close('programmatic', false, true);\n\n registerClickListener(false);\n\n if (positionerCleanup) {\n positionerCleanup();\n positionerCleanup = null;\n }\n };\n\n options.signal.addEventListener('abort', dispose, { once: true });\n\n return {
|
|
1
|
+
{"version":3,"file":"overlay.js","names":[],"sources":["../../src/headless/overlay.ts"],"sourcesContent":["import type { OverlayPositioner } from './positioner';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/**\n * Why an overlay was opened.\n * - `'click'` — user clicked the trigger element\n * - `'focus'` — focus entered the trigger (tooltip, popover)\n * - `'hover'` — pointer entered the trigger (tooltip)\n * - `'keyboard'` — keyboard shortcut or Enter/Space on a trigger\n * - `'programmatic'` — opened via the JS API without a user gesture\n * - `'trigger'` — opened via the Invoker Commands API (`command=\"show-modal\"`)\n */\nexport type OverlayOpenReason = 'click' | 'focus' | 'hover' | 'keyboard' | 'programmatic' | 'trigger';\n\n/** Close reasons valid for modal dialogs (includes 'swipe' for drawer). */\nexport type DialogCloseReason = 'escape' | 'outsideClick' | 'programmatic' | 'swipe' | 'trigger';\n/** Close reasons valid for dropdown overlays (no swipe). */\nexport type DropdownCloseReason = Exclude<DialogCloseReason, 'swipe'>;\n\n/** Detail shape for overlay open events emitted by components. */\nexport type OverlayOpenDetail = { reason: OverlayOpenReason };\n/** Detail shape for overlay close events emitted by components. */\nexport type OverlayCloseDetail = { reason: DialogCloseReason };\n\nexport type { OverlayPositioner };\n\nexport type OverlayControlOptions = {\n getBoundary: () => HTMLElement | null;\n getPanel?: () => HTMLElement | null;\n getTrigger?: () => HTMLElement | null;\n isDisabled?: () => boolean;\n isOpen: () => boolean;\n onClose?: (reason: DialogCloseReason) => void;\n onOpen?: (reason: OverlayOpenReason) => void;\n positioner?: OverlayPositioner;\n restoreFocus?: boolean | (() => boolean);\n setOpen: (next: boolean, reason: OverlayOpenReason | DialogCloseReason) => void;\n /** `AbortSignal` from the component lifecycle. `dispose()` is called automatically on abort. */\n signal: AbortSignal;\n};\n\nexport type OverlayControl = {\n [Symbol.dispose](): void;\n /**\n * Closes the overlay.\n * @param reason — why it's closing (default `'programmatic'`)\n * @param restoreFocus — override per-call focus restoration (default: uses `restoreFocus` option)\n */\n close(reason?: DialogCloseReason, restoreFocus?: boolean): void;\n /** Tears down the overlay: closes silently and removes all event listeners. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /**\n * Opens the overlay.\n * @param reason — why it's opening (default `'programmatic'`)\n */\n open(reason?: OverlayOpenReason): void;\n /**\n * Toggles open state.\n * - Opens with `openReason` (default `'click'`).\n * - Closes with `closeReason` (default `'trigger'`).\n */\n toggle(openReason?: OverlayOpenReason, closeReason?: DialogCloseReason): void;\n};\n\n// ── Factory ───────────────────────────────────────────────────────────────────\nexport const createOverlayControl = (options: OverlayControlOptions): OverlayControl => {\n let positionerCleanup: (() => void) | null = null;\n\n const shouldRestoreFocus = (): boolean => {\n if (typeof options.restoreFocus === 'function') return options.restoreFocus();\n\n return options.restoreFocus ?? true;\n };\n\n // Per-instance outside-pointer listener registered directly on document.\n // Uses 'pointerdown' instead of 'click' so that tapping empty/non-interactive\n // areas on iOS Safari (which does not synthesize 'click' for those targets)\n // still dismisses the overlay.\n // No shared multiplexer needed — max ~3 overlays can be open concurrently.\n const clickListener = (event: Event): void => {\n if (!options.isOpen()) return;\n\n const path = (event as Event & { composedPath?: () => EventTarget[] }).composedPath?.() ?? [];\n const boundary = options.getBoundary();\n const panel = options.getPanel?.() ?? null;\n const target = (path[0] ?? event.target) as EventTarget | null;\n const nodeTarget = target instanceof Node ? target : null;\n const insideByPath = path.some((entry) => entry === boundary || entry === panel);\n const insideByContainment = nodeTarget\n ? (boundary?.contains(nodeTarget) ?? false) || (panel?.contains(nodeTarget) ?? false)\n : false;\n\n if (!insideByPath && !insideByContainment) close('outsideClick');\n };\n\n const registerClickListener = (active: boolean): void => {\n if (active) {\n document.addEventListener('pointerdown', clickListener, { capture: true });\n } else {\n document.removeEventListener('pointerdown', clickListener, { capture: true });\n }\n };\n\n const open = (reason: OverlayOpenReason = 'programmatic'): void => {\n if (options.isDisabled?.() || options.isOpen()) return;\n\n options.setOpen(true, reason);\n registerClickListener(true);\n\n if (options.positioner) {\n options.positioner.update();\n positionerCleanup = options.positioner.startAutoUpdate?.() ?? null;\n }\n\n options.onOpen?.(reason);\n };\n\n const close = (reason: DialogCloseReason = 'programmatic', restoreFocus?: boolean, silent = false): void => {\n if (!options.isOpen()) return;\n\n options.setOpen(false, reason);\n registerClickListener(false);\n\n if (positionerCleanup) {\n positionerCleanup();\n positionerCleanup = null;\n }\n\n const restore = restoreFocus ?? shouldRestoreFocus();\n\n if (restore) options.getTrigger?.()?.focus();\n\n if (!silent) options.onClose?.(reason);\n };\n\n const toggle = (openReason: OverlayOpenReason = 'click', closeReason: DialogCloseReason = 'trigger'): void => {\n if (options.isOpen()) {\n close(closeReason);\n } else {\n open(openReason);\n }\n };\n\n let disposed = false;\n\n const dispose = (): void => {\n if (disposed) return;\n\n disposed = true;\n\n // Close silently — teardown should not fire onClose callbacks.\n if (options.isOpen()) close('programmatic', false, true);\n\n registerClickListener(false);\n\n if (positionerCleanup) {\n positionerCleanup();\n positionerCleanup = null;\n }\n };\n\n options.signal.addEventListener('abort', dispose, { once: true });\n\n return {\n close,\n dispose,\n get disposed() {\n return disposed;\n },\n open,\n [Symbol.dispose]: dispose,\n toggle,\n };\n};\n"],"mappings":";AAoEA,IAAa,KAAwB,MAAmD;CACtF,IAAI,IAAyC,MAEvC,UACA,OAAO,EAAQ,gBAAiB,aAAmB,EAAQ,aAAa,IAErE,EAAQ,gBAAgB,IAQ3B,KAAiB,MAAuB;EAC5C,IAAI,CAAC,EAAQ,OAAO,GAAG;EAEvB,IAAM,IAAQ,EAAyD,eAAe,KAAK,CAAC,GACtF,IAAW,EAAQ,YAAY,GAC/B,IAAQ,EAAQ,WAAW,KAAK,MAChC,IAAU,EAAK,MAAM,EAAM,QAC3B,IAAa,aAAkB,OAAO,IAAS,MAC/C,IAAe,EAAK,MAAM,MAAU,MAAU,KAAY,MAAU,CAAK,GACzE,IAAsB,KACvB,GAAU,SAAS,CAAU,KAAK,QAAW,GAAO,SAAS,CAAU,KAAK,MAC7E;EAEJ,AAAI,CAAC,KAAgB,CAAC,KAAqB,EAAM,cAAc;CACjE,GAEM,KAAyB,MAA0B;EACvD,AAAI,IACF,SAAS,iBAAiB,eAAe,GAAe,EAAE,SAAS,GAAK,CAAC,IAEzE,SAAS,oBAAoB,eAAe,GAAe,EAAE,SAAS,GAAK,CAAC;CAEhF,GAEM,KAAQ,IAA4B,mBAAyB;EAC7D,EAAQ,aAAa,KAAK,EAAQ,OAAO,MAE7C,EAAQ,QAAQ,IAAM,CAAM,GAC5B,EAAsB,EAAI,GAEtB,EAAQ,eACV,EAAQ,WAAW,OAAO,GAC1B,IAAoB,EAAQ,WAAW,kBAAkB,KAAK,OAGhE,EAAQ,SAAS,CAAM;CACzB,GAEM,KAAS,IAA4B,gBAAgB,GAAwB,IAAS,OAAgB;EACrG,EAAQ,OAAO,MAEpB,EAAQ,QAAQ,IAAO,CAAM,GAC7B,EAAsB,EAAK,GAE3B,AAEE,OADA,EAAkB,GACE,QAGN,KAAgB,EAAmB,MAEtC,EAAQ,aAAa,CAAC,EAAE,MAAM,GAEtC,KAAQ,EAAQ,UAAU,CAAM;CACvC,GAEM,KAAU,IAAgC,SAAS,IAAiC,cAAoB;EAC5G,AAAI,EAAQ,OAAO,IACjB,EAAM,CAAW,IAEjB,EAAK,CAAU;CAEnB,GAEI,IAAW,IAET,UAAsB;EACtB,MAEJ,IAAW,IAGP,EAAQ,OAAO,KAAG,EAAM,gBAAgB,IAAO,EAAI,GAEvD,EAAsB,EAAK,GAE3B,AAEE,OADA,EAAkB,GACE;CAExB;CAIA,OAFA,EAAQ,OAAO,iBAAiB,SAAS,GAAS,EAAE,MAAM,GAAK,CAAC,GAEzD;EACL;EACA;EACA,IAAI,WAAW;GACb,OAAO;EACT;EACA;GACC,OAAO,UAAU;EAClB;CACF;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ref-callback.cjs","names":[],"sources":["../../src/headless/ref-callback.ts"],"sourcesContent":["import type { Readable } from '@vielzeug/ripple';\n\nimport { watch } from '@vielzeug/ripple';\n\n/** Shape every `ref` prop in this package uses — a JS-only escape hatch to the live element. */\nexport type RefCallback<T> = ((el: T | null) => void) | null | undefined;\n\n/**\n * Forwards a mounted element to a JS-only `ref` callback prop, and keeps forwarding to a\n * *new* callback if the prop is set again after mount (e.g. a parent sets it via a ref\n * callback after render, rather than at initial mount). Every text-entry-style component\n * with a `ref` escape-hatch prop (`ore-input`, `ore-textarea`, `ore-message-composer`,\n * `ore-file-input`) wires this identically — call it once from inside `onElement()`, alongside\n * whatever else that element needs wired.\n *\n * @example\n * ```ts\n * onElement(fieldRef, (el) => {\n * const unwireField = tf.wire(el);\n * const unwireRef = bindRefCallback(props.ref, el);\n *\n * return () => {\n * unwireField();\n * unwireRef();\n * };\n * });\n * ```\n */\nexport const bindRefCallback = <T extends Element>(ref: Readable<RefCallback<T>>, el: T): (() => void) => {\n // Immediate fire for when the prop is already set on mount.\n ref.value?.(el);\n\n const sub = watch(ref, (cb) => cb?.(el));\n\n return () => {\n sub.dispose();\n ref.value?.(null);\n };\n};\n"],"mappings":"kCA4BA,IAAa,GAAsC,EAA+B,IAAwB,CAExG,EAAI,QAAQ,CAAE,EAEd,IAAM,GAAA,EAAA,EAAA,MAAA,CAAY,EAAM,GAAO,IAAK,CAAE,CAAC,EAEvC,UAAa,CACX,EAAI,QAAQ,EACZ,EAAI,QAAQ,IAAI,CAClB,CACF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Readable } from '@vielzeug/ripple';
|
|
2
|
+
/** Shape every `ref` prop in this package uses — a JS-only escape hatch to the live element. */
|
|
3
|
+
export type RefCallback<T> = ((el: T | null) => void) | null | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* Forwards a mounted element to a JS-only `ref` callback prop, and keeps forwarding to a
|
|
6
|
+
* *new* callback if the prop is set again after mount (e.g. a parent sets it via a ref
|
|
7
|
+
* callback after render, rather than at initial mount). Every text-entry-style component
|
|
8
|
+
* with a `ref` escape-hatch prop (`ore-input`, `ore-textarea`, `ore-message-composer`,
|
|
9
|
+
* `ore-file-input`) wires this identically — call it once from inside `onElement()`, alongside
|
|
10
|
+
* whatever else that element needs wired.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* onElement(fieldRef, (el) => {
|
|
15
|
+
* const unwireField = tf.wire(el);
|
|
16
|
+
* const unwireRef = bindRefCallback(props.ref, el);
|
|
17
|
+
*
|
|
18
|
+
* return () => {
|
|
19
|
+
* unwireField();
|
|
20
|
+
* unwireRef();
|
|
21
|
+
* };
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare const bindRefCallback: <T extends Element>(ref: Readable<RefCallback<T>>, el: T) => (() => void);
|
|
26
|
+
//# sourceMappingURL=ref-callback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ref-callback.d.ts","sourceRoot":"","sources":["../../src/headless/ref-callback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAIjD,gGAAgG;AAChG,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,OAAO,EAAE,KAAK,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,KAAG,CAAC,MAAM,IAAI,CAUpG,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { watch as e } from "@vielzeug/ripple";
|
|
2
|
+
//#region src/headless/ref-callback.ts
|
|
3
|
+
var t = (t, n) => {
|
|
4
|
+
t.value?.(n);
|
|
5
|
+
let r = e(t, (e) => e?.(n));
|
|
6
|
+
return () => {
|
|
7
|
+
r.dispose(), t.value?.(null);
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
export { t as bindRefCallback };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=ref-callback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ref-callback.js","names":[],"sources":["../../src/headless/ref-callback.ts"],"sourcesContent":["import type { Readable } from '@vielzeug/ripple';\n\nimport { watch } from '@vielzeug/ripple';\n\n/** Shape every `ref` prop in this package uses — a JS-only escape hatch to the live element. */\nexport type RefCallback<T> = ((el: T | null) => void) | null | undefined;\n\n/**\n * Forwards a mounted element to a JS-only `ref` callback prop, and keeps forwarding to a\n * *new* callback if the prop is set again after mount (e.g. a parent sets it via a ref\n * callback after render, rather than at initial mount). Every text-entry-style component\n * with a `ref` escape-hatch prop (`ore-input`, `ore-textarea`, `ore-message-composer`,\n * `ore-file-input`) wires this identically — call it once from inside `onElement()`, alongside\n * whatever else that element needs wired.\n *\n * @example\n * ```ts\n * onElement(fieldRef, (el) => {\n * const unwireField = tf.wire(el);\n * const unwireRef = bindRefCallback(props.ref, el);\n *\n * return () => {\n * unwireField();\n * unwireRef();\n * };\n * });\n * ```\n */\nexport const bindRefCallback = <T extends Element>(ref: Readable<RefCallback<T>>, el: T): (() => void) => {\n // Immediate fire for when the prop is already set on mount.\n ref.value?.(el);\n\n const sub = watch(ref, (cb) => cb?.(el));\n\n return () => {\n sub.dispose();\n ref.value?.(null);\n };\n};\n"],"mappings":";;AA4BA,IAAa,KAAsC,GAA+B,MAAwB;CAExG,EAAI,QAAQ,CAAE;CAEd,IAAM,IAAM,EAAM,IAAM,MAAO,IAAK,CAAE,CAAC;CAEvC,aAAa;EAEX,AADA,EAAI,QAAQ,GACZ,EAAI,QAAQ,IAAI;CAClB;AACF"}
|
package/dist/headless/swipe.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e=e=>e.axis?.()??`x`,t=e=>{let t=e.threshold?.()??48;return Number.isFinite(t)&&t>0?t:1},n=(e,t)=>t===`x`?e.clientX:e.clientY,r=e=>e.currentTarget instanceof HTMLElement?e.currentTarget:e.target instanceof HTMLElement?e.target:null,i=i=>{let a=null,o=()=>!!i.disabled?.value,
|
|
1
|
+
var e=e=>e.axis?.()??`x`,t=e=>{let t=e.threshold?.()??48;return Number.isFinite(t)&&t>0?t:1},n=(e,t)=>t===`x`?e.clientX:e.clientY,r=e=>e.currentTarget instanceof HTMLElement?e.currentTarget:e.target instanceof HTMLElement?e.target:null,i=i=>{let a=null,o=!1,s=()=>!!i.disabled?.value,c=()=>{a=null},l=()=>{o=!0,c()},u=e=>a?.pointerId===e.pointerId,d=e=>{if(!a)return null;let t=n(e,a.axis),r=t-a.start;return{axis:a.axis,current:t,distance:r,event:e,pointerId:a.pointerId,progress:Math.min(Math.abs(r)/a.threshold,1),start:a.start,threshold:a.threshold}};return{dispose:l,get disposed(){return o},handlePointerCancel:e=>{if(!a||!u(e))return!1;let t=d(e);return c(),t&&i.onCancel?.(t),!0},handlePointerDown:c=>{if(o||a||s())return!1;let l=e(i);a={axis:l,pointerId:c.pointerId,start:n(c,l),threshold:t(i)},(typeof i.captureTarget==`function`?i.captureTarget(c):r(c))?.setPointerCapture?.(c.pointerId);let u=d(c);return u&&i.onStart?.(u),!0},handlePointerMove:e=>{if(!a||!u(e))return!1;let t=d(e);return t?(i.onMove?.(t),i.shouldCommit?.(t)??Math.abs(t.distance)>=t.threshold?(c(),i.onCommit?.(t),!0):!0):!1},handlePointerUp:e=>{if(!a||!u(e))return!1;let t=d(e);return c(),t&&i.onRelease?.(t),!0},isActive:()=>a!=null,[Symbol.dispose]:l}};exports.createSwipeControl=i;
|
|
2
2
|
//# sourceMappingURL=swipe.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swipe.cjs","names":[],"sources":["../../src/headless/swipe.ts"],"sourcesContent":["import type { Readable } from '@vielzeug/ripple';\n\nexport type SwipeAxis = 'x' | 'y';\n\nexport type SwipeControlDetail = {\n axis: SwipeAxis;\n current: number;\n distance: number;\n event: PointerEvent;\n pointerId: number;\n progress: number;\n start: number;\n threshold: number;\n};\n\nexport type SwipeControlOptions = {\n axis?: () => SwipeAxis;\n captureTarget?: (event: PointerEvent) => HTMLElement | null;\n disabled?: Readable<boolean | undefined>;\n /** Called when the platform interrupts an in-flight swipe (`pointercancel`). */\n onCancel?: (detail: SwipeControlDetail) => void;\n onCommit?: (detail: SwipeControlDetail) => void;\n onMove?: (detail: SwipeControlDetail) => void;\n /** Called when the pointer is released (`pointerup`) without crossing the commit threshold. */\n onRelease?: (detail: SwipeControlDetail) => void;\n onStart?: (detail: SwipeControlDetail) => void;\n shouldCommit?: (detail: SwipeControlDetail) => boolean;\n threshold?: () => number;\n};\n\nexport type SwipeControl = {\n [Symbol.dispose](): void;\n /** Cancel any in-flight swipe and clean up internal state. */\n dispose: () => void;\n handlePointerCancel: (event: PointerEvent) => boolean;\n handlePointerDown: (event: PointerEvent) => boolean;\n handlePointerMove: (event: PointerEvent) => boolean;\n handlePointerUp: (event: PointerEvent) => boolean;\n isActive: () => boolean;\n};\n\ntype ActiveSwipe = {\n axis: SwipeAxis;\n pointerId: number;\n start: number;\n threshold: number;\n};\n\nconst resolveAxis = (options: SwipeControlOptions): SwipeAxis => options.axis?.() ?? 'x';\n\nconst resolveThreshold = (options: SwipeControlOptions): number => {\n const threshold = options.threshold?.() ?? 48;\n\n return Number.isFinite(threshold) && threshold > 0 ? threshold : 1;\n};\n\nconst getCoordinate = (event: PointerEvent, axis: SwipeAxis): number => {\n return axis === 'x' ? event.clientX : event.clientY;\n};\n\nconst defaultCaptureTarget = (event: PointerEvent): HTMLElement | null => {\n if (event.currentTarget instanceof HTMLElement) return event.currentTarget;\n\n if (event.target instanceof HTMLElement) return event.target;\n\n return null;\n};\n\nexport const createSwipeControl = (options: SwipeControlOptions): SwipeControl => {\n let active: ActiveSwipe | null = null;\n\n const isDisabled = (): boolean => Boolean(options.disabled?.value);\n\n const reset = (): void => {\n active = null;\n };\n\n const matchesPointer = (event: PointerEvent): boolean => {\n return active?.pointerId === event.pointerId;\n };\n\n const createDetail = (event: PointerEvent): SwipeControlDetail | null => {\n if (!active) return null;\n\n const current = getCoordinate(event, active.axis);\n const distance = current - active.start;\n\n return {\n axis: active.axis,\n current,\n distance,\n event,\n pointerId: active.pointerId,\n progress: Math.min(Math.abs(distance) / active.threshold, 1),\n start: active.start,\n threshold: active.threshold,\n };\n };\n\n const handlePointerDown = (event: PointerEvent): boolean => {\n if (active || isDisabled()) return false;\n\n const axis = resolveAxis(options);\n\n active = {\n axis,\n pointerId: event.pointerId,\n start: getCoordinate(event, axis),\n threshold: resolveThreshold(options),\n };\n\n const captureTarget =\n typeof options.captureTarget === 'function' ? options.captureTarget(event) : defaultCaptureTarget(event);\n\n captureTarget?.setPointerCapture?.(event.pointerId);\n\n const detail = createDetail(event);\n\n if (detail) options.onStart?.(detail);\n\n return true;\n };\n\n const handlePointerMove = (event: PointerEvent): boolean => {\n if (!active || !matchesPointer(event)) return false;\n\n const detail = createDetail(event);\n\n if (!detail) return false;\n\n options.onMove?.(detail);\n\n const shouldCommit = options.shouldCommit?.(detail) ?? Math.abs(detail.distance) >= detail.threshold;\n\n if (!shouldCommit) return true;\n\n reset();\n options.onCommit?.(detail);\n\n return true;\n };\n\n const handlePointerUp = (event: PointerEvent): boolean => {\n if (!active || !matchesPointer(event)) return false;\n\n const detail = createDetail(event);\n\n reset();\n\n if (detail) options.onRelease?.(detail);\n\n return true;\n };\n\n const handlePointerCancel = (event: PointerEvent): boolean => {\n if (!active || !matchesPointer(event)) return false;\n\n const detail = createDetail(event);\n\n reset();\n\n if (detail) options.onCancel?.(detail);\n\n return true;\n };\n\n return {\n dispose
|
|
1
|
+
{"version":3,"file":"swipe.cjs","names":[],"sources":["../../src/headless/swipe.ts"],"sourcesContent":["import type { Readable } from '@vielzeug/ripple';\n\nexport type SwipeAxis = 'x' | 'y';\n\nexport type SwipeControlDetail = {\n axis: SwipeAxis;\n current: number;\n distance: number;\n event: PointerEvent;\n pointerId: number;\n progress: number;\n start: number;\n threshold: number;\n};\n\nexport type SwipeControlOptions = {\n axis?: () => SwipeAxis;\n captureTarget?: (event: PointerEvent) => HTMLElement | null;\n disabled?: Readable<boolean | undefined>;\n /** Called when the platform interrupts an in-flight swipe (`pointercancel`). */\n onCancel?: (detail: SwipeControlDetail) => void;\n onCommit?: (detail: SwipeControlDetail) => void;\n onMove?: (detail: SwipeControlDetail) => void;\n /** Called when the pointer is released (`pointerup`) without crossing the commit threshold. */\n onRelease?: (detail: SwipeControlDetail) => void;\n onStart?: (detail: SwipeControlDetail) => void;\n shouldCommit?: (detail: SwipeControlDetail) => boolean;\n threshold?: () => number;\n};\n\nexport type SwipeControl = {\n [Symbol.dispose](): void;\n /** Cancel any in-flight swipe and clean up internal state. */\n dispose: () => void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n handlePointerCancel: (event: PointerEvent) => boolean;\n handlePointerDown: (event: PointerEvent) => boolean;\n handlePointerMove: (event: PointerEvent) => boolean;\n handlePointerUp: (event: PointerEvent) => boolean;\n isActive: () => boolean;\n};\n\ntype ActiveSwipe = {\n axis: SwipeAxis;\n pointerId: number;\n start: number;\n threshold: number;\n};\n\nconst resolveAxis = (options: SwipeControlOptions): SwipeAxis => options.axis?.() ?? 'x';\n\nconst resolveThreshold = (options: SwipeControlOptions): number => {\n const threshold = options.threshold?.() ?? 48;\n\n return Number.isFinite(threshold) && threshold > 0 ? threshold : 1;\n};\n\nconst getCoordinate = (event: PointerEvent, axis: SwipeAxis): number => {\n return axis === 'x' ? event.clientX : event.clientY;\n};\n\nconst defaultCaptureTarget = (event: PointerEvent): HTMLElement | null => {\n if (event.currentTarget instanceof HTMLElement) return event.currentTarget;\n\n if (event.target instanceof HTMLElement) return event.target;\n\n return null;\n};\n\nexport const createSwipeControl = (options: SwipeControlOptions): SwipeControl => {\n let active: ActiveSwipe | null = null;\n let disposed = false;\n\n const isDisabled = (): boolean => Boolean(options.disabled?.value);\n\n const reset = (): void => {\n active = null;\n };\n\n const dispose = (): void => {\n disposed = true;\n reset();\n };\n\n const matchesPointer = (event: PointerEvent): boolean => {\n return active?.pointerId === event.pointerId;\n };\n\n const createDetail = (event: PointerEvent): SwipeControlDetail | null => {\n if (!active) return null;\n\n const current = getCoordinate(event, active.axis);\n const distance = current - active.start;\n\n return {\n axis: active.axis,\n current,\n distance,\n event,\n pointerId: active.pointerId,\n progress: Math.min(Math.abs(distance) / active.threshold, 1),\n start: active.start,\n threshold: active.threshold,\n };\n };\n\n const handlePointerDown = (event: PointerEvent): boolean => {\n if (disposed || active || isDisabled()) return false;\n\n const axis = resolveAxis(options);\n\n active = {\n axis,\n pointerId: event.pointerId,\n start: getCoordinate(event, axis),\n threshold: resolveThreshold(options),\n };\n\n const captureTarget =\n typeof options.captureTarget === 'function' ? options.captureTarget(event) : defaultCaptureTarget(event);\n\n captureTarget?.setPointerCapture?.(event.pointerId);\n\n const detail = createDetail(event);\n\n if (detail) options.onStart?.(detail);\n\n return true;\n };\n\n const handlePointerMove = (event: PointerEvent): boolean => {\n if (!active || !matchesPointer(event)) return false;\n\n const detail = createDetail(event);\n\n if (!detail) return false;\n\n options.onMove?.(detail);\n\n const shouldCommit = options.shouldCommit?.(detail) ?? Math.abs(detail.distance) >= detail.threshold;\n\n if (!shouldCommit) return true;\n\n reset();\n options.onCommit?.(detail);\n\n return true;\n };\n\n const handlePointerUp = (event: PointerEvent): boolean => {\n if (!active || !matchesPointer(event)) return false;\n\n const detail = createDetail(event);\n\n reset();\n\n if (detail) options.onRelease?.(detail);\n\n return true;\n };\n\n const handlePointerCancel = (event: PointerEvent): boolean => {\n if (!active || !matchesPointer(event)) return false;\n\n const detail = createDetail(event);\n\n reset();\n\n if (detail) options.onCancel?.(detail);\n\n return true;\n };\n\n return {\n dispose,\n get disposed() {\n return disposed;\n },\n handlePointerCancel,\n handlePointerDown,\n handlePointerMove,\n handlePointerUp,\n isActive: () => active != null,\n [Symbol.dispose]: dispose,\n };\n};\n"],"mappings":"AAkDA,IAAM,EAAe,GAA4C,EAAQ,OAAO,GAAK,IAE/E,EAAoB,GAAyC,CACjE,IAAM,EAAY,EAAQ,YAAY,GAAK,GAE3C,OAAO,OAAO,SAAS,CAAS,GAAK,EAAY,EAAI,EAAY,CACnE,EAEM,GAAiB,EAAqB,IACnC,IAAS,IAAM,EAAM,QAAU,EAAM,QAGxC,EAAwB,GACxB,EAAM,yBAAyB,YAAoB,EAAM,cAEzD,EAAM,kBAAkB,YAAoB,EAAM,OAE/C,KAGI,EAAsB,GAA+C,CAChF,IAAI,EAA6B,KAC7B,EAAW,GAET,MAA4B,EAAQ,EAAQ,UAAU,MAEtD,MAAoB,CACxB,EAAS,IACX,EAEM,MAAsB,CAC1B,EAAW,GACX,EAAM,CACR,EAEM,EAAkB,GACf,GAAQ,YAAc,EAAM,UAG/B,EAAgB,GAAmD,CACvE,GAAI,CAAC,EAAQ,OAAO,KAEpB,IAAM,EAAU,EAAc,EAAO,EAAO,IAAI,EAC1C,EAAW,EAAU,EAAO,MAElC,MAAO,CACL,KAAM,EAAO,KACb,UACA,WACA,QACA,UAAW,EAAO,UAClB,SAAU,KAAK,IAAI,KAAK,IAAI,CAAQ,EAAI,EAAO,UAAW,CAAC,EAC3D,MAAO,EAAO,MACd,UAAW,EAAO,SACpB,CACF,EAqEA,MAAO,CACL,UACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,oBAjB2B,GAAiC,CAC5D,GAAI,CAAC,GAAU,CAAC,EAAe,CAAK,EAAG,MAAO,GAE9C,IAAM,EAAS,EAAa,CAAK,EAMjC,OAJA,EAAM,EAEF,GAAQ,EAAQ,WAAW,CAAM,EAE9B,EACT,EAQE,kBAzEyB,GAAiC,CAC1D,GAAI,GAAY,GAAU,EAAW,EAAG,MAAO,GAE/C,IAAM,EAAO,EAAY,CAAO,EAEhC,EAAS,CACP,OACA,UAAW,EAAM,UACjB,MAAO,EAAc,EAAO,CAAI,EAChC,UAAW,EAAiB,CAAO,CACrC,GAGE,OAAO,EAAQ,eAAkB,WAAa,EAAQ,cAAc,CAAK,EAAI,EAAqB,CAAK,EAAA,EAE1F,oBAAoB,EAAM,SAAS,EAElD,IAAM,EAAS,EAAa,CAAK,EAIjC,OAFI,GAAQ,EAAQ,UAAU,CAAM,EAE7B,EACT,EAoDE,kBAlDyB,GAAiC,CAC1D,GAAI,CAAC,GAAU,CAAC,EAAe,CAAK,EAAG,MAAO,GAE9C,IAAM,EAAS,EAAa,CAAK,EAajC,OAXK,GAEL,EAAQ,SAAS,CAAM,EAEF,EAAQ,eAAe,CAAM,GAAK,KAAK,IAAI,EAAO,QAAQ,GAAK,EAAO,WAI3F,EAAM,EACN,EAAQ,WAAW,CAAM,EAElB,IALmB,IANN,EAYtB,EAkCE,gBAhCuB,GAAiC,CACxD,GAAI,CAAC,GAAU,CAAC,EAAe,CAAK,EAAG,MAAO,GAE9C,IAAM,EAAS,EAAa,CAAK,EAMjC,OAJA,EAAM,EAEF,GAAQ,EAAQ,YAAY,CAAM,EAE/B,EACT,EAuBE,aAAgB,GAAU,MACzB,OAAO,SAAU,CACpB,CACF"}
|
package/dist/headless/swipe.d.ts
CHANGED
|
@@ -28,6 +28,8 @@ export type SwipeControl = {
|
|
|
28
28
|
[Symbol.dispose](): void;
|
|
29
29
|
/** Cancel any in-flight swipe and clean up internal state. */
|
|
30
30
|
dispose: () => void;
|
|
31
|
+
/** `true` after `dispose()` has been called. */
|
|
32
|
+
readonly disposed: boolean;
|
|
31
33
|
handlePointerCancel: (event: PointerEvent) => boolean;
|
|
32
34
|
handlePointerDown: (event: PointerEvent) => boolean;
|
|
33
35
|
handlePointerMove: (event: PointerEvent) => boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swipe.d.ts","sourceRoot":"","sources":["../../src/headless/swipe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,MAAM,SAAS,GAAG,GAAG,GAAG,GAAG,CAAC;AAElC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,CAAC,EAAE,MAAM,SAAS,CAAC;IACvB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,WAAW,GAAG,IAAI,CAAC;IAC5D,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACzC,gFAAgF;IAChF,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAChD,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAChD,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC9C,+FAA+F;IAC/F,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC/C,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,OAAO,CAAC;IACvD,SAAS,CAAC,EAAE,MAAM,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;IACzB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,mBAAmB,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IACtD,iBAAiB,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IACpD,iBAAiB,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IACpD,eAAe,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IAClD,QAAQ,EAAE,MAAM,OAAO,CAAC;CACzB,CAAC;AA6BF,eAAO,MAAM,kBAAkB,GAAI,SAAS,mBAAmB,KAAG,
|
|
1
|
+
{"version":3,"file":"swipe.d.ts","sourceRoot":"","sources":["../../src/headless/swipe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,MAAM,SAAS,GAAG,GAAG,GAAG,GAAG,CAAC;AAElC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,CAAC,EAAE,MAAM,SAAS,CAAC;IACvB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,WAAW,GAAG,IAAI,CAAC;IAC5D,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACzC,gFAAgF;IAChF,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAChD,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAChD,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC9C,+FAA+F;IAC/F,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC/C,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,OAAO,CAAC;IACvD,SAAS,CAAC,EAAE,MAAM,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;IACzB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gDAAgD;IAChD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,mBAAmB,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IACtD,iBAAiB,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IACpD,iBAAiB,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IACpD,eAAe,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IAClD,QAAQ,EAAE,MAAM,OAAO,CAAC;CACzB,CAAC;AA6BF,eAAO,MAAM,kBAAkB,GAAI,SAAS,mBAAmB,KAAG,YAoHjE,CAAC"}
|
package/dist/headless/swipe.js
CHANGED
|
@@ -3,9 +3,11 @@ var e = (e) => e.axis?.() ?? "x", t = (e) => {
|
|
|
3
3
|
let t = e.threshold?.() ?? 48;
|
|
4
4
|
return Number.isFinite(t) && t > 0 ? t : 1;
|
|
5
5
|
}, n = (e, t) => t === "x" ? e.clientX : e.clientY, r = (e) => e.currentTarget instanceof HTMLElement ? e.currentTarget : e.target instanceof HTMLElement ? e.target : null, i = (i) => {
|
|
6
|
-
let a = null, o = () => !!i.disabled?.value,
|
|
6
|
+
let a = null, o = !1, s = () => !!i.disabled?.value, c = () => {
|
|
7
7
|
a = null;
|
|
8
|
-
},
|
|
8
|
+
}, l = () => {
|
|
9
|
+
o = !0, c();
|
|
10
|
+
}, u = (e) => a?.pointerId === e.pointerId, d = (e) => {
|
|
9
11
|
if (!a) return null;
|
|
10
12
|
let t = n(e, a.axis), r = t - a.start;
|
|
11
13
|
return {
|
|
@@ -20,36 +22,39 @@ var e = (e) => e.axis?.() ?? "x", t = (e) => {
|
|
|
20
22
|
};
|
|
21
23
|
};
|
|
22
24
|
return {
|
|
23
|
-
dispose:
|
|
25
|
+
dispose: l,
|
|
26
|
+
get disposed() {
|
|
27
|
+
return o;
|
|
28
|
+
},
|
|
24
29
|
handlePointerCancel: (e) => {
|
|
25
|
-
if (!a || !
|
|
26
|
-
let t =
|
|
27
|
-
return
|
|
30
|
+
if (!a || !u(e)) return !1;
|
|
31
|
+
let t = d(e);
|
|
32
|
+
return c(), t && i.onCancel?.(t), !0;
|
|
28
33
|
},
|
|
29
|
-
handlePointerDown: (
|
|
30
|
-
if (a ||
|
|
31
|
-
let
|
|
34
|
+
handlePointerDown: (c) => {
|
|
35
|
+
if (o || a || s()) return !1;
|
|
36
|
+
let l = e(i);
|
|
32
37
|
a = {
|
|
33
|
-
axis:
|
|
34
|
-
pointerId:
|
|
35
|
-
start: n(
|
|
38
|
+
axis: l,
|
|
39
|
+
pointerId: c.pointerId,
|
|
40
|
+
start: n(c, l),
|
|
36
41
|
threshold: t(i)
|
|
37
|
-
}, (typeof i.captureTarget == "function" ? i.captureTarget(
|
|
38
|
-
let u =
|
|
42
|
+
}, (typeof i.captureTarget == "function" ? i.captureTarget(c) : r(c))?.setPointerCapture?.(c.pointerId);
|
|
43
|
+
let u = d(c);
|
|
39
44
|
return u && i.onStart?.(u), !0;
|
|
40
45
|
},
|
|
41
46
|
handlePointerMove: (e) => {
|
|
42
|
-
if (!a || !
|
|
43
|
-
let t =
|
|
44
|
-
return t ? (i.onMove?.(t), i.shouldCommit?.(t) ?? Math.abs(t.distance) >= t.threshold ? (
|
|
47
|
+
if (!a || !u(e)) return !1;
|
|
48
|
+
let t = d(e);
|
|
49
|
+
return t ? (i.onMove?.(t), i.shouldCommit?.(t) ?? Math.abs(t.distance) >= t.threshold ? (c(), i.onCommit?.(t), !0) : !0) : !1;
|
|
45
50
|
},
|
|
46
51
|
handlePointerUp: (e) => {
|
|
47
|
-
if (!a || !
|
|
48
|
-
let t =
|
|
49
|
-
return
|
|
52
|
+
if (!a || !u(e)) return !1;
|
|
53
|
+
let t = d(e);
|
|
54
|
+
return c(), t && i.onRelease?.(t), !0;
|
|
50
55
|
},
|
|
51
56
|
isActive: () => a != null,
|
|
52
|
-
[Symbol.dispose]:
|
|
57
|
+
[Symbol.dispose]: l
|
|
53
58
|
};
|
|
54
59
|
};
|
|
55
60
|
//#endregion
|