sheer-ui 0.4.0 → 0.4.2
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/package.json +1 -1
- package/src/lib/components/accordion/components/accordion-item.svelte +3 -3
- package/src/lib/components/accordion/components/accordion.svelte +29 -13
- package/src/lib/components/accordion/types.ts +3 -3
- package/src/lib/components/checkbox/components/checkbox-group.svelte +7 -5
- package/src/lib/components/checkbox/components/checkbox.svelte +12 -8
- package/src/lib/components/combobox/components/combobox.svelte +2 -2
- package/src/lib/components/combobox/select/components/select.svelte +2 -2
- package/src/lib/components/context-menu/components/context-menu-trigger.svelte +1 -1
- package/src/lib/components/date-field/date-field.svelte.ts +2 -3
- package/src/lib/components/menu/components/menu-checkbox-group.svelte +6 -4
- package/src/lib/components/menu/components/menu-checkbox-item.svelte +11 -7
- package/src/lib/components/menu/menu.svelte.ts +9 -18
- package/src/lib/components/menubar/menubar.svelte.ts +7 -6
- package/src/lib/components/navigation-menu/components/navigation-menu-content.svelte +1 -1
- package/src/lib/components/navigation-menu/components/navigation-menu-indicator.svelte +2 -2
- package/src/lib/components/navigation-menu/components/navigation-menu-trigger.svelte +1 -1
- package/src/lib/components/navigation-menu/navigation-menu.svelte.ts +57 -60
- package/src/lib/components/pagination/components/pagination-next-button.svelte +12 -7
- package/src/lib/components/pagination/components/pagination-page.svelte +15 -8
- package/src/lib/components/pagination/components/pagination-prev-button.svelte +12 -7
- package/src/lib/components/pagination/components/pagination.svelte +31 -18
- package/src/lib/components/pagination/pagination.svelte.ts +48 -64
- package/src/lib/components/pin-input/pin-input.svelte.ts +7 -8
- package/src/lib/components/pin-input/usePasswordManager.svelte.ts +2 -2
- package/src/lib/components/scroll-area/scroll-area.svelte.ts +9 -10
- package/src/lib/components/slider/components/slider.svelte +2 -1
- package/src/lib/components/tabs/components/tabs-content.svelte +12 -7
- package/src/lib/components/tabs/components/tabs-list.svelte +9 -6
- package/src/lib/components/tabs/components/tabs-trigger.svelte +15 -8
- package/src/lib/components/tabs/components/tabs.svelte +28 -17
- package/src/lib/components/tabs/tabs.svelte.ts +51 -67
- package/src/lib/components/time-field/time-field.svelte.ts +2 -2
- package/src/lib/components/toggle-group/components/toggle-group-item.svelte +15 -8
- package/src/lib/components/toggle-group/components/toggle-group.svelte +28 -16
- package/src/lib/components/toggle-group/toggle-group.svelte.ts +33 -35
- package/src/lib/components/toolbar/components/toolbar-group-item.svelte +15 -8
- package/src/lib/components/toolbar/components/toolbar-group.svelte +19 -14
- package/src/lib/components/toolbar/toolbar.svelte.ts +28 -30
- package/src/lib/components/tooltip/tooltip.svelte.ts +6 -27
- package/src/lib/internal/dismissible-layer/use-dismissable-layer.svelte.ts +3 -4
- package/src/lib/internal/floating-layer/use-floating-layer.svelte.ts +26 -30
- package/src/lib/internal/{roving-focus-group.ts → roving-focus-group.svelte.ts} +17 -19
- package/src/lib/internal/roving-focus-item.svelte.ts +12 -17
- package/src/lib/internal/selection.svelte.ts +55 -41
- package/src/lib/internal/{state-machine.ts → state-machine.svelte.ts} +6 -8
- package/src/lib/internal/timeout-fn.svelte.ts +6 -4
- package/src/lib/internal/tools/box.svelte.ts +5 -114
- package/src/lib/internal/tools/index.ts +1 -11
- package/src/lib/internal/tools/types.ts +0 -3
- package/src/lib/internal/typeahead.svelte.ts +9 -13
- package/src/lib/internal/types.ts +6 -0
- package/src/lib/internal/vaul/use-drawer-content.svelte.ts +2 -2
- package/src/lib/internal/vaul/use-drawer-root.svelte.ts +12 -7
- package/src/lib/components/accordion/accordion.svelte.ts +0 -43
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
import { createContext } from 'svelte';
|
|
2
|
-
import { type ReadableBox, type ReadableBoxedValues,
|
|
2
|
+
import { type ReadableBox, type ReadableBoxedValues, attachRef } from '../../internal/tools/index.js';
|
|
3
3
|
import { createBitsAttrs, boolToEmptyStrOrUndef, boolToTrueOrUndef } from '../../internal/attrs.js';
|
|
4
4
|
import type { Orientation } from '../../internal/index.js';
|
|
5
5
|
import type { BitsKeyboardEvent, RefAttachment, WithRefOpts } from '../../internal/types.js';
|
|
6
|
-
import { RovingFocusGroup } from '../../internal/roving-focus-group.js';
|
|
6
|
+
import { RovingFocusGroup } from '../../internal/roving-focus-group.svelte.js';
|
|
7
7
|
import { RovingFocusItem } from '../../internal/roving-focus-item.svelte.js';
|
|
8
|
-
import {
|
|
9
|
-
type SelectionGroup,
|
|
10
|
-
type SelectionItemOpts,
|
|
11
|
-
type SelectionType,
|
|
12
|
-
SelectionItemState,
|
|
13
|
-
SelectionValue,
|
|
14
|
-
} from '../../internal/selection.svelte.js';
|
|
8
|
+
import { type SelectionGroup, type SelectionItemOpts, SelectionItemState, SelectionValue } from '../../internal/selection.svelte.js';
|
|
15
9
|
|
|
16
10
|
export const toolbarAttrs = createBitsAttrs({
|
|
17
11
|
component: 'toolbar',
|
|
@@ -41,9 +35,9 @@ export class ToolbarRootState {
|
|
|
41
35
|
this.attachment = attachRef(this.opts.ref);
|
|
42
36
|
|
|
43
37
|
this.rovingFocusGroup = new RovingFocusGroup({
|
|
44
|
-
orientation: this.opts.orientation,
|
|
45
|
-
loop: this.opts.loop,
|
|
46
|
-
rootNode: this.opts.ref,
|
|
38
|
+
orientation: () => this.opts.orientation.current,
|
|
39
|
+
loop: () => this.opts.loop.current,
|
|
40
|
+
rootNode: () => this.opts.ref.current,
|
|
47
41
|
candidateAttr: toolbarAttrs.item,
|
|
48
42
|
});
|
|
49
43
|
}
|
|
@@ -60,14 +54,12 @@ export class ToolbarRootState {
|
|
|
60
54
|
);
|
|
61
55
|
}
|
|
62
56
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
type: SelectionType;
|
|
70
|
-
value: WritableBox<string | string[]>;
|
|
57
|
+
/** The group component's props as accessors over its `$props()`; `ref` writes back to its bindable. */
|
|
58
|
+
interface ToolbarGroupStateOpts {
|
|
59
|
+
readonly id: string;
|
|
60
|
+
readonly disabled: boolean;
|
|
61
|
+
ref: HTMLElement | null;
|
|
62
|
+
selection: SelectionValue;
|
|
71
63
|
}
|
|
72
64
|
|
|
73
65
|
/** A selection group inside the toolbar: its items rove with the toolbar's other items. */
|
|
@@ -78,8 +70,6 @@ export class ToolbarGroupState implements SelectionGroup {
|
|
|
78
70
|
readonly opts: ToolbarGroupStateOpts;
|
|
79
71
|
readonly root: ToolbarRootState;
|
|
80
72
|
readonly selection: SelectionValue;
|
|
81
|
-
readonly disabled: ReadableBox<boolean>;
|
|
82
|
-
readonly orientation: ReadableBox<Orientation>;
|
|
83
73
|
readonly rovingFocusGroup: RovingFocusGroup;
|
|
84
74
|
readonly selectionTakesTabStop = false;
|
|
85
75
|
readonly extraItemAttrs = { [toolbarAttrs['group-item']]: '' } as const;
|
|
@@ -88,21 +78,27 @@ export class ToolbarGroupState implements SelectionGroup {
|
|
|
88
78
|
constructor(opts: ToolbarGroupStateOpts, root: ToolbarRootState) {
|
|
89
79
|
this.opts = opts;
|
|
90
80
|
this.root = root;
|
|
91
|
-
this.selection =
|
|
92
|
-
this.disabled = opts.disabled;
|
|
93
|
-
this.orientation = root.opts.orientation;
|
|
81
|
+
this.selection = opts.selection;
|
|
94
82
|
this.rovingFocusGroup = root.rovingFocusGroup;
|
|
95
|
-
this.attachment = attachRef(
|
|
83
|
+
this.attachment = attachRef<HTMLElement>((v) => (opts.ref = v));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
get disabled() {
|
|
87
|
+
return this.opts.disabled;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
get orientation() {
|
|
91
|
+
return this.root.opts.orientation.current;
|
|
96
92
|
}
|
|
97
93
|
|
|
98
94
|
readonly props = $derived.by(
|
|
99
95
|
() =>
|
|
100
96
|
({
|
|
101
|
-
id: this.opts.id
|
|
97
|
+
id: this.opts.id,
|
|
102
98
|
[toolbarAttrs.group]: '',
|
|
103
99
|
role: 'group',
|
|
104
100
|
'data-orientation': this.root.opts.orientation.current,
|
|
105
|
-
'data-disabled': boolToEmptyStrOrUndef(this.opts.disabled
|
|
101
|
+
'data-disabled': boolToEmptyStrOrUndef(this.opts.disabled),
|
|
106
102
|
...this.attachment,
|
|
107
103
|
}) as const,
|
|
108
104
|
);
|
|
@@ -127,7 +123,8 @@ export class ToolbarLinkState {
|
|
|
127
123
|
this.root = root;
|
|
128
124
|
this.rovingItem = new RovingFocusItem({
|
|
129
125
|
group: this.root.rovingFocusGroup,
|
|
130
|
-
ref: this.opts.ref,
|
|
126
|
+
ref: () => this.opts.ref.current,
|
|
127
|
+
setRef: (v) => (this.opts.ref.current = v),
|
|
131
128
|
});
|
|
132
129
|
|
|
133
130
|
this.onkeydown = this.onkeydown.bind(this);
|
|
@@ -179,7 +176,8 @@ export class ToolbarButtonState {
|
|
|
179
176
|
this.root = root;
|
|
180
177
|
this.rovingItem = new RovingFocusItem({
|
|
181
178
|
group: this.root.rovingFocusGroup,
|
|
182
|
-
ref: this.opts.ref,
|
|
179
|
+
ref: () => this.opts.ref.current,
|
|
180
|
+
setRef: (v) => (this.opts.ref.current = v),
|
|
183
181
|
});
|
|
184
182
|
|
|
185
183
|
this.onkeydown = this.onkeydown.bind(this);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { attachRef, DOMContext, type WritableBoxedValues, type ReadableBoxedValues
|
|
1
|
+
import { attachRef, DOMContext, type WritableBoxedValues, type ReadableBoxedValues } from '../../internal/tools/index.js';
|
|
2
2
|
import { on } from 'svelte/events';
|
|
3
3
|
import { createContext, onMount, untrack } from 'svelte';
|
|
4
4
|
import { isElement } from '../../internal/tools/utils/dom.js';
|
|
@@ -145,7 +145,6 @@ export class TooltipProviderState {
|
|
|
145
145
|
}
|
|
146
146
|
readonly opts: TooltipProviderStateOpts;
|
|
147
147
|
isOpenDelayed = $state<boolean>(true);
|
|
148
|
-
isPointerInTransit = simpleBox(false);
|
|
149
148
|
readonly #timerFn: ReturnType<typeof createEffectTimeout<() => void>>;
|
|
150
149
|
#openTooltip = $state<TooltipRootState | null>(null);
|
|
151
150
|
|
|
@@ -436,18 +435,9 @@ export class TooltipTriggerState {
|
|
|
436
435
|
readonly root: TooltipRootState | null;
|
|
437
436
|
readonly tether: TooltipTetherState | null;
|
|
438
437
|
readonly attachment: RefAttachment;
|
|
439
|
-
#isPointerDown =
|
|
438
|
+
#isPointerDown = false;
|
|
440
439
|
#hasPointerMoveOpened = $state(false);
|
|
441
440
|
domContext: DOMContext;
|
|
442
|
-
// Fired on pointerenter while the pointer is in transit: if still in transit after the delay, the
|
|
443
|
-
// user is likely staying on this trigger.
|
|
444
|
-
#transitCheck = createEffectTimeout(() => {
|
|
445
|
-
const root = this.#getRoot();
|
|
446
|
-
if (!root || !root.provider.isPointerInTransit.current) return;
|
|
447
|
-
root.provider.isPointerInTransit.current = false;
|
|
448
|
-
root.onTriggerEnter(this.opts.id.current);
|
|
449
|
-
this.#hasPointerMoveOpened = true;
|
|
450
|
-
}, () => 250);
|
|
451
441
|
#mounted = false;
|
|
452
442
|
#lastRegisteredId: string | null = null;
|
|
453
443
|
|
|
@@ -551,17 +541,17 @@ export class TooltipTriggerState {
|
|
|
551
541
|
};
|
|
552
542
|
|
|
553
543
|
handlePointerUp = () => {
|
|
554
|
-
this.#isPointerDown
|
|
544
|
+
this.#isPointerDown = false;
|
|
555
545
|
};
|
|
556
546
|
|
|
557
547
|
#onpointerup: PointerEventHandler<HTMLElement> = () => {
|
|
558
548
|
if (this.#isDisabled()) return;
|
|
559
|
-
this.#isPointerDown
|
|
549
|
+
this.#isPointerDown = false;
|
|
560
550
|
};
|
|
561
551
|
|
|
562
552
|
#onpointerdown: PointerEventHandler<HTMLElement> = () => {
|
|
563
553
|
if (this.#isDisabled()) return;
|
|
564
|
-
this.#isPointerDown
|
|
554
|
+
this.#isPointerDown = true;
|
|
565
555
|
|
|
566
556
|
on(
|
|
567
557
|
this.domContext.getDocument(),
|
|
@@ -584,12 +574,6 @@ export class TooltipTriggerState {
|
|
|
584
574
|
}
|
|
585
575
|
if (e.pointerType === 'touch') return;
|
|
586
576
|
|
|
587
|
-
// if in transit, wait briefly to see if user is actually heading to old content or staying here
|
|
588
|
-
if (root.provider.isPointerInTransit.current) {
|
|
589
|
-
this.#transitCheck.start();
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
577
|
root.onTriggerEnter(this.opts.id.current);
|
|
594
578
|
this.#hasPointerMoveOpened = true;
|
|
595
579
|
};
|
|
@@ -606,10 +590,6 @@ export class TooltipTriggerState {
|
|
|
606
590
|
if (e.pointerType === 'touch') return;
|
|
607
591
|
if (this.#hasPointerMoveOpened) return;
|
|
608
592
|
|
|
609
|
-
// moving within trigger means we're definitely not in transit anymore
|
|
610
|
-
this.#transitCheck.stop();
|
|
611
|
-
root.provider.isPointerInTransit.current = false;
|
|
612
|
-
|
|
613
593
|
root.onTriggerEnter(this.opts.id.current);
|
|
614
594
|
this.#hasPointerMoveOpened = true;
|
|
615
595
|
};
|
|
@@ -618,7 +598,6 @@ export class TooltipTriggerState {
|
|
|
618
598
|
const root = this.#getRoot();
|
|
619
599
|
if (!root) return;
|
|
620
600
|
if (this.#isDisabled()) return;
|
|
621
|
-
this.#transitCheck.stop();
|
|
622
601
|
if (!root.isActiveTrigger(this.opts.id.current)) {
|
|
623
602
|
this.#hasPointerMoveOpened = false;
|
|
624
603
|
return;
|
|
@@ -649,7 +628,7 @@ export class TooltipTriggerState {
|
|
|
649
628
|
#onfocus: FocusEventHandler<HTMLElement> = (e) => {
|
|
650
629
|
const root = this.#getRoot();
|
|
651
630
|
if (!root) return;
|
|
652
|
-
if (this.#isPointerDown
|
|
631
|
+
if (this.#isPointerDown) return;
|
|
653
632
|
if (this.#isDisabled()) {
|
|
654
633
|
if (root.opts.open.current) {
|
|
655
634
|
root.handleClose();
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
simpleBox,
|
|
3
2
|
boxWith,
|
|
4
3
|
attachRef,
|
|
5
4
|
type Getter,
|
|
@@ -219,7 +218,7 @@ export function interactOutsideAttachment(opts: {
|
|
|
219
218
|
enabled: Getter<boolean>;
|
|
220
219
|
isValidEvent?: Getter<((e: PointerEvent, node: HTMLElement) => boolean) | undefined>;
|
|
221
220
|
}): { props: { onfocuscapture: () => void; onblurcapture: () => void }; attachment: RefAttachment<HTMLElement> } {
|
|
222
|
-
|
|
221
|
+
let node = $state.raw<HTMLElement | null>(null);
|
|
223
222
|
const state = DismissibleLayerState.create({
|
|
224
223
|
id: boxWith(opts.id),
|
|
225
224
|
interactOutsideBehavior: boxWith(opts.interactOutsideBehavior),
|
|
@@ -227,9 +226,9 @@ export function interactOutsideAttachment(opts: {
|
|
|
227
226
|
enabled: boxWith(opts.enabled),
|
|
228
227
|
onFocusOutside: boxWith(() => opts.onFocusOutside?.()),
|
|
229
228
|
isValidEvent: boxWith(() => opts.isValidEvent?.()),
|
|
230
|
-
ref,
|
|
229
|
+
ref: boxWith(() => node, (v) => (node = v)),
|
|
231
230
|
});
|
|
232
|
-
return { props: state.props, attachment: attachRef(
|
|
231
|
+
return { props: state.props, attachment: attachRef((v) => (node = v)) };
|
|
233
232
|
}
|
|
234
233
|
|
|
235
234
|
function isValidEvent(e: PointerEvent, node: HTMLElement): boolean {
|
|
@@ -5,10 +5,7 @@ import {
|
|
|
5
5
|
getWindow,
|
|
6
6
|
styleToString,
|
|
7
7
|
type ReadableBoxedValues,
|
|
8
|
-
type ReadableBox,
|
|
9
8
|
type RefAttachment,
|
|
10
|
-
simpleBox,
|
|
11
|
-
boxWith,
|
|
12
9
|
} from '../tools/index.js';
|
|
13
10
|
import type { WithRefOpts } from '../types.js';
|
|
14
11
|
import type { Direction, StyleProperties } from '../index.js';
|
|
@@ -60,10 +57,16 @@ export class FloatingRootState {
|
|
|
60
57
|
return tooltip ? setFloatingTooltipRoot(root) : setFloatingRoot(root);
|
|
61
58
|
}
|
|
62
59
|
readonly anchorName: string;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
// Getters over whoever owns the anchor (trigger attachment, customAnchor, pointer rect): a derived reading through them tracks the owner.
|
|
61
|
+
#customAnchorSource: () => CustomAnchorNode = $state.raw(() => null);
|
|
62
|
+
triggerSource: () => AnchorNode = $state.raw(() => null);
|
|
63
|
+
|
|
64
|
+
constructor(anchorName: string) {
|
|
65
|
+
this.anchorName = anchorName;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get anchorNode(): AnchorNode {
|
|
69
|
+
const customAnchor = this.#customAnchorSource();
|
|
67
70
|
|
|
68
71
|
if (customAnchor) {
|
|
69
72
|
if (typeof customAnchor === 'string') {
|
|
@@ -73,18 +76,14 @@ export class FloatingRootState {
|
|
|
73
76
|
return customAnchor;
|
|
74
77
|
}
|
|
75
78
|
|
|
76
|
-
return this.triggerSource
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
constructor(anchorName: string) {
|
|
80
|
-
this.anchorName = anchorName;
|
|
79
|
+
return this.triggerSource();
|
|
81
80
|
}
|
|
82
81
|
|
|
83
|
-
get triggerNode() {
|
|
84
|
-
return this.triggerSource;
|
|
82
|
+
get triggerNode(): AnchorNode {
|
|
83
|
+
return this.triggerSource();
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
setCustomAnchorSource(source:
|
|
86
|
+
setCustomAnchorSource(source: () => CustomAnchorNode) {
|
|
88
87
|
this.#customAnchorSource = source;
|
|
89
88
|
}
|
|
90
89
|
}
|
|
@@ -134,12 +133,9 @@ export class FloatingContentState {
|
|
|
134
133
|
#arrowWidth = $state(0);
|
|
135
134
|
#arrowHeight = $state(0);
|
|
136
135
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
arrowRef = simpleBox<HTMLElement | null>(null);
|
|
140
|
-
readonly contentAttachment = attachRef(this.contentRef);
|
|
136
|
+
contentRef = $state.raw<HTMLElement | null>(null);
|
|
137
|
+
readonly contentAttachment = attachRef<HTMLElement>((v) => (this.contentRef = v));
|
|
141
138
|
readonly arrowAttachment = {
|
|
142
|
-
...attachRef(this.arrowRef),
|
|
143
139
|
[createAttachmentKey()]: ((node) => this.#measureArrow(node)) satisfies Attachment<HTMLElement>,
|
|
144
140
|
};
|
|
145
141
|
|
|
@@ -244,7 +240,7 @@ export class FloatingContentState {
|
|
|
244
240
|
/** A virtual anchor (a rect, no element) is rendered as a zero-size fixed box carrying the anchor name. */
|
|
245
241
|
virtualAnchorStyle = $derived.by(() => {
|
|
246
242
|
if (!this.opts.present.current) return null;
|
|
247
|
-
const anchor = this.root.anchorNode
|
|
243
|
+
const anchor = this.root.anchorNode;
|
|
248
244
|
// SSR-safe: on the server there is no HTMLElement (and no virtual anchor either)
|
|
249
245
|
if (!anchor || typeof HTMLElement === 'undefined' || anchor instanceof HTMLElement) return null;
|
|
250
246
|
const rect = anchor.getBoundingClientRect();
|
|
@@ -300,12 +296,12 @@ export class FloatingContentState {
|
|
|
300
296
|
constructor(opts: FloatingContentStateOpts, root: FloatingRootState) {
|
|
301
297
|
this.opts = opts;
|
|
302
298
|
this.root = root;
|
|
303
|
-
this.root.setCustomAnchorSource(opts.customAnchor);
|
|
299
|
+
this.root.setCustomAnchorSource(() => opts.customAnchor.current);
|
|
304
300
|
|
|
305
301
|
// An element anchor that is not the trigger (a `customAnchor`) needs the anchor name too.
|
|
306
302
|
$effect(() => {
|
|
307
|
-
const anchor = this.root.anchorNode
|
|
308
|
-
if (!(anchor instanceof HTMLElement) || anchor === this.root.triggerNode
|
|
303
|
+
const anchor = this.root.anchorNode;
|
|
304
|
+
if (!(anchor instanceof HTMLElement) || anchor === this.root.triggerNode) return;
|
|
309
305
|
const previous = anchor.style.anchorName;
|
|
310
306
|
anchor.style.anchorName = this.root.anchorName;
|
|
311
307
|
return () => {
|
|
@@ -326,7 +322,7 @@ export class FloatingContentState {
|
|
|
326
322
|
this.#measuredPlacement = null;
|
|
327
323
|
return;
|
|
328
324
|
}
|
|
329
|
-
const content = this.contentRef
|
|
325
|
+
const content = this.contentRef;
|
|
330
326
|
if (!content) return;
|
|
331
327
|
const win = getWindow(content);
|
|
332
328
|
let raf = win.requestAnimationFrame(() => this.#measurePlacement());
|
|
@@ -345,8 +341,8 @@ export class FloatingContentState {
|
|
|
345
341
|
}
|
|
346
342
|
|
|
347
343
|
#measurePlacement() {
|
|
348
|
-
const content = this.contentRef
|
|
349
|
-
const anchor = this.root.anchorNode
|
|
344
|
+
const content = this.contentRef;
|
|
345
|
+
const anchor = this.root.anchorNode;
|
|
350
346
|
if (!content || !anchor) return;
|
|
351
347
|
const c = content.getBoundingClientRect();
|
|
352
348
|
const a = anchor.getBoundingClientRect();
|
|
@@ -444,7 +440,7 @@ export class FloatingArrowState {
|
|
|
444
440
|
* tracking the pointer, which the content renders as a zero-size anchor element. Reads the
|
|
445
441
|
* floating-root context, so call it during component init.
|
|
446
442
|
*/
|
|
447
|
-
export function setFloatingAnchor(source:
|
|
443
|
+
export function setFloatingAnchor(source: () => AnchorNode, tooltip = false) {
|
|
448
444
|
const root = tooltip ? getFloatingTooltipRoot() : getFloatingRoot();
|
|
449
445
|
root.triggerSource = source;
|
|
450
446
|
}
|
|
@@ -460,11 +456,11 @@ export function floatingAnchor(tooltip = false): RefAttachment {
|
|
|
460
456
|
return {
|
|
461
457
|
[createAttachmentKey()]: (node) => {
|
|
462
458
|
const el = node as HTMLElement;
|
|
463
|
-
root.triggerSource =
|
|
459
|
+
root.triggerSource = () => el;
|
|
464
460
|
el.style.anchorName = root.anchorName;
|
|
465
461
|
return () => {
|
|
466
462
|
el.style.removeProperty('anchor-name');
|
|
467
|
-
root.triggerSource =
|
|
463
|
+
root.triggerSource = () => null;
|
|
468
464
|
};
|
|
469
465
|
},
|
|
470
466
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { Getter } from './tools/index.js';
|
|
2
2
|
import { getDirectionalKeys, kbd } from './kbd.js';
|
|
3
3
|
import type { Direction, Orientation } from './index.js';
|
|
4
4
|
import { isHTMLElement } from './tools/utils/dom.js';
|
|
@@ -30,21 +30,19 @@ type RovingFocusGroupOptions = (
|
|
|
30
30
|
candidateSelector?: undefined;
|
|
31
31
|
}
|
|
32
32
|
) & {
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
*/
|
|
36
|
-
rootNode: Box<HTMLElement | null>;
|
|
33
|
+
/** The node the candidates live under. */
|
|
34
|
+
rootNode: Getter<HTMLElement | null>;
|
|
37
35
|
|
|
38
36
|
/**
|
|
39
37
|
* Whether to loop through the candidates when reaching the end.
|
|
40
38
|
*/
|
|
41
|
-
loop:
|
|
39
|
+
loop: Getter<boolean>;
|
|
42
40
|
|
|
43
41
|
/**
|
|
44
42
|
* The orientation of the roving focus group. Used
|
|
45
43
|
* to determine how keyboard navigation should work.
|
|
46
44
|
*/
|
|
47
|
-
orientation:
|
|
45
|
+
orientation: Getter<Orientation>;
|
|
48
46
|
|
|
49
47
|
/**
|
|
50
48
|
* A callback function called when a candidate is focused.
|
|
@@ -54,7 +52,7 @@ type RovingFocusGroupOptions = (
|
|
|
54
52
|
|
|
55
53
|
export class RovingFocusGroup {
|
|
56
54
|
readonly #opts: RovingFocusGroupOptions;
|
|
57
|
-
|
|
55
|
+
#currentTabStopId = $state.raw<string | null>(null);
|
|
58
56
|
/** What a candidate of an attribute-marked group stamps on itself; empty for the other kinds. */
|
|
59
57
|
readonly candidateAttrs: Record<string, ''>;
|
|
60
58
|
|
|
@@ -64,7 +62,7 @@ export class RovingFocusGroup {
|
|
|
64
62
|
}
|
|
65
63
|
|
|
66
64
|
getCandidateNodes() {
|
|
67
|
-
const rootNode = this.#opts.rootNode
|
|
65
|
+
const rootNode = this.#opts.rootNode();
|
|
68
66
|
if (!BROWSER || !rootNode) return [];
|
|
69
67
|
if (this.#opts.candidateNodes) return this.#opts.candidateNodes(rootNode);
|
|
70
68
|
|
|
@@ -79,7 +77,7 @@ export class RovingFocusGroup {
|
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
handleKeydown(node: HTMLElement | null | undefined, e: KeyboardEvent, both: boolean = false) {
|
|
82
|
-
const rootNode = this.#opts.rootNode
|
|
80
|
+
const rootNode = this.#opts.rootNode();
|
|
83
81
|
if (!rootNode || !node) return;
|
|
84
82
|
|
|
85
83
|
const items = this.getCandidateNodes();
|
|
@@ -87,8 +85,8 @@ export class RovingFocusGroup {
|
|
|
87
85
|
|
|
88
86
|
const currentIndex = items.indexOf(node);
|
|
89
87
|
const dir = (rootNode.ownerDocument.defaultView ?? window).getComputedStyle(rootNode).direction as Direction;
|
|
90
|
-
const { nextKey, prevKey } = getDirectionalKeys(dir, this.#opts.orientation
|
|
91
|
-
const loop = this.#opts.loop
|
|
88
|
+
const { nextKey, prevKey } = getDirectionalKeys(dir, this.#opts.orientation());
|
|
89
|
+
const loop = this.#opts.loop();
|
|
92
90
|
|
|
93
91
|
const keyToIndex = {
|
|
94
92
|
[nextKey]: currentIndex + 1,
|
|
@@ -117,19 +115,19 @@ export class RovingFocusGroup {
|
|
|
117
115
|
const itemToFocus = items[itemIndex];
|
|
118
116
|
if (!itemToFocus) return;
|
|
119
117
|
itemToFocus.focus();
|
|
120
|
-
this.#currentTabStopId
|
|
118
|
+
this.#currentTabStopId = itemToFocus.id;
|
|
121
119
|
this.#opts.onCandidateFocus?.(itemToFocus);
|
|
122
120
|
return itemToFocus;
|
|
123
121
|
}
|
|
124
122
|
|
|
125
123
|
getTabIndex(node: HTMLElement | null | undefined) {
|
|
126
124
|
const items = this.getCandidateNodes();
|
|
127
|
-
const anyActive = this.#currentTabStopId
|
|
125
|
+
const anyActive = this.#currentTabStopId !== null;
|
|
128
126
|
|
|
129
127
|
if (node && !anyActive && items[0] === node) {
|
|
130
|
-
this.#currentTabStopId
|
|
128
|
+
this.#currentTabStopId = node.id;
|
|
131
129
|
return 0;
|
|
132
|
-
} else if (node?.id === this.#currentTabStopId
|
|
130
|
+
} else if (node?.id === this.#currentTabStopId) {
|
|
133
131
|
return 0;
|
|
134
132
|
}
|
|
135
133
|
|
|
@@ -137,13 +135,13 @@ export class RovingFocusGroup {
|
|
|
137
135
|
}
|
|
138
136
|
|
|
139
137
|
setCurrentTabStopId(id: string) {
|
|
140
|
-
this.#currentTabStopId
|
|
138
|
+
this.#currentTabStopId = id;
|
|
141
139
|
}
|
|
142
140
|
|
|
143
141
|
focusCurrentTabStop() {
|
|
144
|
-
const currentTabStopId = this.#currentTabStopId
|
|
142
|
+
const currentTabStopId = this.#currentTabStopId;
|
|
145
143
|
if (!currentTabStopId) return;
|
|
146
|
-
const currentTabStop = this.#opts.rootNode
|
|
144
|
+
const currentTabStop = this.#opts.rootNode()?.querySelector(`#${currentTabStopId}`);
|
|
147
145
|
if (!currentTabStop || !isHTMLElement(currentTabStop)) return;
|
|
148
146
|
currentTabStop.focus();
|
|
149
147
|
}
|
|
@@ -1,21 +1,16 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import type { RovingFocusGroup } from './roving-focus-group.js';
|
|
3
|
-
|
|
4
|
-
type MaybeReadableBoolean = boolean | ReadableBox<boolean>;
|
|
1
|
+
import { type Getter, type RefAttachment, attachRef } from './tools/index.js';
|
|
2
|
+
import type { RovingFocusGroup } from './roving-focus-group.svelte.js';
|
|
5
3
|
|
|
6
4
|
type RovingFocusItemOptions<T extends HTMLElement = HTMLElement> = {
|
|
7
5
|
group: RovingFocusGroup;
|
|
8
|
-
ref:
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
ref: Getter<T | null>;
|
|
7
|
+
/** Receives the node from the attachment; the owner keeps it wherever its `ref` reads from. */
|
|
8
|
+
setRef: (node: T | null) => void;
|
|
9
|
+
/** Whether arrow keys move from this item; absent means always. */
|
|
10
|
+
enabled?: Getter<boolean>;
|
|
11
11
|
onRefChange?: (node: T | null) => void;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
function readBoolean(value: MaybeReadableBoolean | undefined, fallback: boolean) {
|
|
15
|
-
if (value === undefined) return fallback;
|
|
16
|
-
return typeof value === 'boolean' ? value : value.current;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
14
|
export class RovingFocusItem<T extends HTMLElement = HTMLElement> {
|
|
20
15
|
readonly #opts: RovingFocusItemOptions<T>;
|
|
21
16
|
readonly attachment: RefAttachment<T>;
|
|
@@ -23,22 +18,22 @@ export class RovingFocusItem<T extends HTMLElement = HTMLElement> {
|
|
|
23
18
|
|
|
24
19
|
constructor(opts: RovingFocusItemOptions<T>) {
|
|
25
20
|
this.#opts = opts;
|
|
26
|
-
this.attachment = attachRef(opts.
|
|
21
|
+
this.attachment = attachRef<T>(opts.setRef, opts.onRefChange);
|
|
27
22
|
this.handleKeydown = this.handleKeydown.bind(this);
|
|
28
23
|
|
|
29
24
|
$effect(() => {
|
|
30
|
-
if (!
|
|
25
|
+
if (!(this.#opts.enabled?.() ?? true)) {
|
|
31
26
|
this.#tabIndex = 0;
|
|
32
27
|
return;
|
|
33
28
|
}
|
|
34
29
|
|
|
35
|
-
this.#tabIndex = this.#opts.group.getTabIndex(this.#opts.ref
|
|
30
|
+
this.#tabIndex = this.#opts.group.getTabIndex(this.#opts.ref());
|
|
36
31
|
});
|
|
37
32
|
}
|
|
38
33
|
|
|
39
34
|
handleKeydown(e: KeyboardEvent) {
|
|
40
|
-
if (!
|
|
41
|
-
return this.#opts.group.handleKeydown(this.#opts.ref
|
|
35
|
+
if (!(this.#opts.enabled?.() ?? true)) return;
|
|
36
|
+
return this.#opts.group.handleKeydown(this.#opts.ref(), e);
|
|
42
37
|
}
|
|
43
38
|
|
|
44
39
|
readonly props = $derived.by(
|