sheer-ui 0.1.0 → 0.3.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.
@@ -1,20 +1,25 @@
1
1
  import { createContext } from 'svelte';
2
- import { type WritableBox, type ReadableBoxedValues, type WritableBoxedValues, attachRef } from '../../internal/tools/index.js';
3
- import { createBitsAttrs, getAriaChecked, boolToStr, boolToEmptyStrOrUndef, boolToTrueOrUndef } from '../../internal/attrs.js';
4
- import { kbd } from '../../internal/kbd.js';
2
+ import { type ReadableBox, type ReadableBoxedValues, type WritableBox, attachRef } from '../../internal/tools/index.js';
3
+ import { createBitsAttrs, boolToEmptyStrOrUndef } from '../../internal/attrs.js';
5
4
  import type { Orientation } from '../../internal/index.js';
6
- import type { BitsKeyboardEvent, BitsMouseEvent, RefAttachment, WithRefOpts } from '../../internal/types.js';
5
+ import type { RefAttachment, WithRefOpts } from '../../internal/types.js';
7
6
  import { RovingFocusGroup } from '../../internal/roving-focus-group.js';
8
- import { RovingFocusItem } from '../../internal/roving-focus-item.svelte.js';
7
+ import {
8
+ type SelectionGroup,
9
+ type SelectionItemOpts,
10
+ type SelectionType,
11
+ SelectionItemState,
12
+ SelectionValue,
13
+ } from '../../internal/selection.svelte.js';
9
14
 
10
15
  export const toggleGroupAttrs = createBitsAttrs({
11
16
  component: 'toggle-group',
12
17
  parts: ['root', 'item'],
13
18
  });
14
19
 
15
- const [getToggleGroupRoot, setToggleGroupRoot] = createContext<ToggleGroup>();
20
+ const [getToggleGroupRoot, setToggleGroupRoot] = createContext<ToggleGroupRootState>();
16
21
 
17
- interface ToggleGroupBaseStateOpts
22
+ interface ToggleGroupRootStateOpts
18
23
  extends
19
24
  WithRefOpts,
20
25
  ReadableBoxedValues<{
@@ -22,16 +27,31 @@ interface ToggleGroupBaseStateOpts
22
27
  rovingFocus: boolean;
23
28
  loop: boolean;
24
29
  orientation: Orientation;
25
- }> {}
30
+ }> {
31
+ type: SelectionType;
32
+ value: WritableBox<string | string[]>;
33
+ }
26
34
 
27
- abstract class ToggleGroupBaseState {
28
- readonly opts: ToggleGroupBaseStateOpts;
35
+ export class ToggleGroupRootState implements SelectionGroup {
36
+ static create(opts: ToggleGroupRootStateOpts) {
37
+ return setToggleGroupRoot(new ToggleGroupRootState(opts));
38
+ }
39
+ readonly opts: ToggleGroupRootStateOpts;
40
+ readonly selection: SelectionValue;
41
+ readonly disabled: ReadableBox<boolean>;
42
+ readonly orientation: ReadableBox<Orientation>;
43
+ readonly rovingFocus: ReadableBox<boolean>;
29
44
  readonly rovingFocusGroup: RovingFocusGroup;
45
+ readonly selectionTakesTabStop = true;
30
46
  readonly attachment: RefAttachment;
31
47
 
32
- constructor(opts: ToggleGroupBaseStateOpts) {
48
+ constructor(opts: ToggleGroupRootStateOpts) {
33
49
  this.opts = opts;
34
- this.attachment = attachRef(this.opts.ref);
50
+ this.selection = new SelectionValue(opts.type, opts.value);
51
+ this.disabled = opts.disabled;
52
+ this.orientation = opts.orientation;
53
+ this.rovingFocus = opts.rovingFocus;
54
+ this.attachment = attachRef(opts.ref);
35
55
  this.rovingFocusGroup = new RovingFocusGroup({
36
56
  candidateAttr: toggleGroupAttrs.item,
37
57
  rootNode: opts.ref,
@@ -53,183 +73,6 @@ abstract class ToggleGroupBaseState {
53
73
  );
54
74
  }
55
75
 
56
- interface ToggleGroupSingleStateOpts
57
- extends
58
- ToggleGroupBaseStateOpts,
59
- WritableBoxedValues<{
60
- value: string;
61
- }> {}
62
-
63
- class ToggleGroupSingleState extends ToggleGroupBaseState {
64
- override readonly opts: ToggleGroupSingleStateOpts;
65
- isMulti = false;
66
- readonly anyPressed = $derived.by(() => this.opts.value.current !== '');
67
-
68
- constructor(opts: ToggleGroupSingleStateOpts) {
69
- super(opts);
70
- this.opts = opts;
71
- }
72
-
73
- includesItem(item: string) {
74
- return this.opts.value.current === item;
75
- }
76
-
77
- toggleItem(item: string, id: string) {
78
- if (this.includesItem(item)) {
79
- this.opts.value.current = '';
80
- } else {
81
- this.opts.value.current = item;
82
- this.rovingFocusGroup.setCurrentTabStopId(id);
83
- }
84
- }
85
- }
86
-
87
- //
88
- // MULTIPLE
89
- //
90
-
91
- interface ToggleGroupMultipleStateOpts
92
- extends
93
- ToggleGroupBaseStateOpts,
94
- WritableBoxedValues<{
95
- value: string[];
96
- }> {}
97
-
98
- class ToggleGroupMultipleState extends ToggleGroupBaseState {
99
- override readonly opts: ToggleGroupMultipleStateOpts;
100
- isMulti = true;
101
- readonly anyPressed = $derived.by(() => this.opts.value.current.length > 0);
102
-
103
- constructor(opts: ToggleGroupMultipleStateOpts) {
104
- super(opts);
105
-
106
- this.opts = opts;
107
- }
108
-
109
- includesItem(item: string) {
110
- return this.opts.value.current.includes(item);
111
- }
112
-
113
- toggleItem(item: string, id: string) {
114
- if (this.includesItem(item)) {
115
- this.opts.value.current = this.opts.value.current.filter((v) => v !== item);
116
- } else {
117
- this.opts.value.current = [...this.opts.value.current, item];
118
- this.rovingFocusGroup.setCurrentTabStopId(id);
119
- }
120
- }
121
- }
122
-
123
- type ToggleGroup = ToggleGroupSingleState | ToggleGroupMultipleState;
124
-
125
- interface ToggleGroupRootOpts
126
- extends
127
- WithRefOpts,
128
- ReadableBoxedValues<{
129
- disabled: boolean;
130
- rovingFocus: boolean;
131
- loop: boolean;
132
- orientation: Orientation;
133
- }> {
134
- type: 'single' | 'multiple';
135
- value: WritableBox<string> | WritableBox<string[]>;
136
- }
137
-
138
- export class ToggleGroupRootState {
139
- static create(opts: ToggleGroupRootOpts): ToggleGroup {
140
- const { type, ...rest } = opts;
141
- const rootState =
142
- type === 'single'
143
- ? new ToggleGroupSingleState(rest as ToggleGroupSingleStateOpts)
144
- : new ToggleGroupMultipleState(rest as ToggleGroupMultipleStateOpts);
145
- return setToggleGroupRoot(rootState);
146
- }
76
+ export function createToggleGroupItem(opts: SelectionItemOpts) {
77
+ return new SelectionItemState(opts, getToggleGroupRoot());
147
78
  }
148
-
149
- interface ToggleGroupItemStateOpts
150
- extends
151
- WithRefOpts,
152
- ReadableBoxedValues<{
153
- value: string;
154
- disabled: boolean;
155
- }> {}
156
-
157
- export class ToggleGroupItemState {
158
- static create(opts: ToggleGroupItemStateOpts) {
159
- return new ToggleGroupItemState(opts, getToggleGroupRoot());
160
- }
161
- readonly opts: ToggleGroupItemStateOpts;
162
- readonly root: ToggleGroup;
163
- readonly rovingItem: RovingFocusItem;
164
- readonly #isDisabled = $derived.by(() => this.opts.disabled.current || this.root.opts.disabled.current);
165
- readonly isPressed = $derived.by(() => this.root.includesItem(this.opts.value.current));
166
-
167
- readonly #ariaChecked = $derived.by(() => {
168
- return this.root.isMulti ? undefined : getAriaChecked(this.isPressed, false);
169
- });
170
-
171
- readonly #ariaPressed = $derived.by(() => {
172
- return this.root.isMulti ? boolToStr(this.isPressed) : undefined;
173
- });
174
-
175
- constructor(opts: ToggleGroupItemStateOpts, root: ToggleGroup) {
176
- this.opts = opts;
177
- this.root = root;
178
- this.rovingItem = new RovingFocusItem({
179
- group: this.root.rovingFocusGroup,
180
- ref: this.opts.ref,
181
- enabled: this.root.opts.rovingFocus,
182
- });
183
-
184
- this.onclick = this.onclick.bind(this);
185
- this.onkeydown = this.onkeydown.bind(this);
186
- }
187
-
188
- #toggleItem() {
189
- if (this.#isDisabled) return;
190
- this.root.toggleItem(this.opts.value.current, this.opts.id.current);
191
- }
192
-
193
- onclick(_: BitsMouseEvent) {
194
- if (this.#isDisabled) return;
195
- this.root.toggleItem(this.opts.value.current, this.opts.id.current);
196
- }
197
-
198
- onkeydown(e: BitsKeyboardEvent) {
199
- if (this.#isDisabled) return;
200
- if (e.key === kbd.ENTER || e.key === kbd.SPACE) {
201
- e.preventDefault();
202
- this.#toggleItem();
203
- return;
204
- }
205
- if (!this.root.opts.rovingFocus.current) return;
206
-
207
- this.rovingItem.handleKeydown(e);
208
- }
209
-
210
- readonly snippetProps = $derived.by(() => ({
211
- pressed: this.isPressed,
212
- }));
213
-
214
- readonly props = $derived.by(
215
- () =>
216
- ({
217
- id: this.opts.id.current,
218
- role: this.root.isMulti ? undefined : 'radio',
219
- 'data-orientation': this.root.opts.orientation.current,
220
- 'data-disabled': boolToEmptyStrOrUndef(this.#isDisabled),
221
- 'data-state': getToggleItemDataState(this.isPressed),
222
- 'data-value': this.opts.value.current,
223
- 'aria-pressed': this.#ariaPressed,
224
- 'aria-checked': this.#ariaChecked,
225
- disabled: boolToTrueOrUndef(this.#isDisabled),
226
- [toggleGroupAttrs.item]: '',
227
- //
228
- onclick: this.onclick,
229
- onkeydown: this.onkeydown,
230
- ...this.rovingItem.props,
231
- }) as const,
232
- );
233
- }
234
-
235
- const getToggleItemDataState = (condition: boolean) => condition ? 'on' : 'off';
@@ -2,7 +2,7 @@
2
2
  import { boxWith } from '../../../internal/tools/index.js';
3
3
  import { mergeProps } from '../../../internal/merge-props.js';
4
4
  import type { ToolbarGroupItemProps } from '../types.js';
5
- import { ToolbarGroupItemState } from '../toolbar.svelte.js';
5
+ import { createToolbarGroupItem } from '../toolbar.svelte.js';
6
6
  import { createId } from '../../../internal/create-id.js';
7
7
  import { toggleVariants, type ToggleSize, type ToggleVariant } from '../../toggle/variants.js';
8
8
 
@@ -24,7 +24,7 @@
24
24
  size?: ToggleSize;
25
25
  } = $props();
26
26
 
27
- const groupItemState = ToolbarGroupItemState.create({
27
+ const groupItemState = createToolbarGroupItem({
28
28
  id: boxWith(() => id),
29
29
  value: boxWith(() => value),
30
30
  disabled: boxWith(() => disabled ?? false),
@@ -48,9 +48,9 @@
48
48
  </script>
49
49
 
50
50
  {#if child}
51
- {@render child({ props: mergedProps, pressed: groupItemState.isPressed })}
51
+ {@render child({ props: mergedProps, ...groupItemState.snippetProps })}
52
52
  {:else}
53
53
  <button {...mergedProps}>
54
- {@render children?.({ pressed: groupItemState.isPressed })}
54
+ {@render children?.(groupItemState.snippetProps)}
55
55
  </button>
56
56
  {/if}
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { untrack } from 'svelte';
3
- import { type WritableBox, boxWith } from '../../../internal/tools/index.js';
3
+ import { boxWith, repairBindable } from '../../../internal/tools/index.js';
4
+ import { emptySelection } from '../../../internal/selection.svelte.js';
4
5
  import { mergeProps } from '../../../internal/merge-props.js';
5
6
  import type { ToolbarGroupProps } from '../types.js';
6
7
  import { ToolbarGroupState } from '../toolbar.svelte.js';
@@ -20,37 +21,29 @@
20
21
  ...restProps
21
22
  }: ToolbarGroupProps = $props();
22
23
 
23
- // Mode is construction-static: ToolbarGroupState chooses a single/multiple class once.
24
+ // Mode is construction-static: the selection stays single or multiple for the group's life.
24
25
  const valueType = untrack(() => type);
25
26
 
26
- function getDefaultValue(): string | string[] {
27
- return valueType === 'single' ? '' : [];
28
- }
29
-
30
- function handleDefaultValue() {
31
- if (value !== undefined) return;
32
- value = getDefaultValue();
33
- }
34
-
35
- function getValue() {
36
- return value ?? getDefaultValue();
37
- }
38
-
39
- // SSR
40
- handleDefaultValue();
27
+ // The group owns a mode-specific controlled value.
28
+ repairBindable(
29
+ () => value,
30
+ () => {
31
+ if (value === undefined) value = emptySelection(valueType);
32
+ },
33
+ );
41
34
 
42
35
  const groupState = ToolbarGroupState.create({
43
36
  id: boxWith(() => id),
44
37
  disabled: boxWith(() => disabled),
45
38
  type: valueType,
46
39
  value: boxWith(
47
- () => getValue(),
40
+ () => value ?? emptySelection(valueType),
48
41
  (v) => {
49
42
  value = v;
50
- // @ts-expect-error - we know
51
- onValueChange(v);
43
+ // oxlint-disable-next-line no-explicit-any
44
+ onValueChange(v as any);
52
45
  },
53
- ) as WritableBox<string> | WritableBox<string[]>,
46
+ ),
54
47
  ref: boxWith(
55
48
  () => ref,
56
49
  (v) => (ref = v),
@@ -1,11 +1,17 @@
1
1
  import { createContext } from 'svelte';
2
- import { type WritableBox, type WritableBoxedValues, type ReadableBoxedValues, attachRef } from '../../internal/tools/index.js';
3
- import { createBitsAttrs, getAriaChecked, boolToStr, boolToEmptyStrOrUndef, boolToTrueOrUndef } from '../../internal/attrs.js';
4
- import { kbd } from '../../internal/kbd.js';
2
+ import { type ReadableBox, type ReadableBoxedValues, type WritableBox, attachRef } from '../../internal/tools/index.js';
3
+ import { createBitsAttrs, boolToEmptyStrOrUndef, boolToTrueOrUndef } from '../../internal/attrs.js';
5
4
  import type { Orientation } from '../../internal/index.js';
6
- import type { BitsKeyboardEvent, BitsMouseEvent, RefAttachment, WithRefOpts } from '../../internal/types.js';
5
+ import type { BitsKeyboardEvent, RefAttachment, WithRefOpts } from '../../internal/types.js';
7
6
  import { RovingFocusGroup } from '../../internal/roving-focus-group.js';
8
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';
9
15
 
10
16
  export const toolbarAttrs = createBitsAttrs({
11
17
  component: 'toolbar',
@@ -13,7 +19,7 @@ export const toolbarAttrs = createBitsAttrs({
13
19
  });
14
20
 
15
21
  const [getToolbarRoot, setToolbarRoot] = createContext<ToolbarRootState>();
16
- const [getToolbarGroup, setToolbarGroup] = createContext<ToolbarGroup>();
22
+ const [getToolbarGroup, setToolbarGroup] = createContext<ToolbarGroupState>();
17
23
  interface ToolbarRootStateOpts
18
24
  extends
19
25
  WithRefOpts,
@@ -54,21 +60,38 @@ export class ToolbarRootState {
54
60
  );
55
61
  }
56
62
 
57
- interface ToolbarGroupBaseStateOpts
63
+ interface ToolbarGroupStateOpts
58
64
  extends
59
65
  WithRefOpts,
60
66
  ReadableBoxedValues<{
61
67
  disabled: boolean;
62
- }> {}
68
+ }> {
69
+ type: SelectionType;
70
+ value: WritableBox<string | string[]>;
71
+ }
63
72
 
64
- abstract class ToolbarGroupBaseState {
65
- readonly opts: ToolbarGroupBaseStateOpts;
73
+ /** A selection group inside the toolbar: its items rove with the toolbar's other items. */
74
+ export class ToolbarGroupState implements SelectionGroup {
75
+ static create(opts: ToolbarGroupStateOpts) {
76
+ return setToolbarGroup(new ToolbarGroupState(opts, getToolbarRoot()));
77
+ }
78
+ readonly opts: ToolbarGroupStateOpts;
66
79
  readonly root: ToolbarRootState;
80
+ readonly selection: SelectionValue;
81
+ readonly disabled: ReadableBox<boolean>;
82
+ readonly orientation: ReadableBox<Orientation>;
83
+ readonly rovingFocusGroup: RovingFocusGroup;
84
+ readonly selectionTakesTabStop = false;
85
+ readonly extraItemAttrs = { [toolbarAttrs['group-item']]: '' } as const;
67
86
  readonly attachment: RefAttachment;
68
87
 
69
- constructor(opts: ToolbarGroupBaseStateOpts, root: ToolbarRootState) {
88
+ constructor(opts: ToolbarGroupStateOpts, root: ToolbarRootState) {
70
89
  this.opts = opts;
71
90
  this.root = root;
91
+ this.selection = new SelectionValue(opts.type, opts.value);
92
+ this.disabled = opts.disabled;
93
+ this.orientation = root.opts.orientation;
94
+ this.rovingFocusGroup = root.rovingFocusGroup;
72
95
  this.attachment = attachRef(this.opts.ref);
73
96
  }
74
97
 
@@ -85,184 +108,8 @@ abstract class ToolbarGroupBaseState {
85
108
  );
86
109
  }
87
110
 
88
- interface ToolbarGroupSingleStateOpts
89
- extends
90
- ToolbarGroupBaseStateOpts,
91
- WritableBoxedValues<{
92
- value: string;
93
- }> {}
94
-
95
- class ToolbarGroupSingleState extends ToolbarGroupBaseState {
96
- override readonly opts: ToolbarGroupSingleStateOpts;
97
- override readonly root: ToolbarRootState;
98
- readonly isMulti = false as const;
99
- readonly anyPressed = $derived.by(() => this.opts.value.current !== '');
100
-
101
- constructor(opts: ToolbarGroupSingleStateOpts, root: ToolbarRootState) {
102
- super(opts, root);
103
-
104
- this.opts = opts;
105
- this.root = root;
106
- }
107
-
108
- includesItem(item: string) {
109
- return this.opts.value.current === item;
110
- }
111
-
112
- toggleItem(item: string) {
113
- if (this.includesItem(item)) {
114
- this.opts.value.current = '';
115
- } else {
116
- this.opts.value.current = item;
117
- }
118
- }
119
- }
120
-
121
- interface ToolbarGroupMultipleStateOpts
122
- extends
123
- ToolbarGroupBaseStateOpts,
124
- WritableBoxedValues<{
125
- value: string[];
126
- }> {}
127
-
128
- class ToolbarGroupMultipleState extends ToolbarGroupBaseState {
129
- override readonly opts: ToolbarGroupMultipleStateOpts;
130
- override readonly root: ToolbarRootState;
131
- readonly isMulti = true as const;
132
- readonly anyPressed = $derived.by(() => this.opts.value.current.length > 0);
133
-
134
- constructor(opts: ToolbarGroupMultipleStateOpts, root: ToolbarRootState) {
135
- super(opts, root);
136
-
137
- this.opts = opts;
138
- this.root = root;
139
- }
140
-
141
- includesItem(item: string) {
142
- return this.opts.value.current.includes(item);
143
- }
144
-
145
- toggleItem(item: string) {
146
- if (this.includesItem(item)) {
147
- this.opts.value.current = this.opts.value.current.filter((v) => v !== item);
148
- } else {
149
- this.opts.value.current = [...this.opts.value.current, item];
150
- }
151
- }
152
- }
153
-
154
- type ToolbarGroup = ToolbarGroupSingleState | ToolbarGroupMultipleState;
155
-
156
- interface ToolbarGroupRootOpts
157
- extends
158
- WithRefOpts,
159
- ReadableBoxedValues<{
160
- disabled: boolean;
161
- }> {
162
- type: 'single' | 'multiple';
163
- value: WritableBox<string> | WritableBox<string[]>;
164
- }
165
-
166
- export class ToolbarGroupState {
167
- static create(opts: ToolbarGroupRootOpts): ToolbarGroup {
168
- const { type, ...rest } = opts;
169
- const rootState = getToolbarRoot();
170
- const groupState =
171
- type === 'single'
172
- ? new ToolbarGroupSingleState(rest as ToolbarGroupSingleStateOpts, rootState)
173
- : new ToolbarGroupMultipleState(rest as ToolbarGroupMultipleStateOpts, rootState);
174
-
175
- return setToolbarGroup(groupState);
176
- }
177
- }
178
-
179
- //
180
- // ITEM
181
- //
182
-
183
- interface ToolbarGroupItemStateOpts
184
- extends
185
- WithRefOpts,
186
- ReadableBoxedValues<{
187
- value: string;
188
- disabled: boolean;
189
- }> {}
190
-
191
- export class ToolbarGroupItemState {
192
- static create(opts: ToolbarGroupItemStateOpts) {
193
- const group = getToolbarGroup();
194
- return new ToolbarGroupItemState(opts, group, group.root);
195
- }
196
- readonly opts: ToolbarGroupItemStateOpts;
197
- readonly group: ToolbarGroup;
198
- readonly root: ToolbarRootState;
199
- readonly rovingItem: RovingFocusItem;
200
- readonly #isDisabled = $derived.by(() => this.opts.disabled.current || this.group.opts.disabled.current);
201
-
202
- constructor(opts: ToolbarGroupItemStateOpts, group: ToolbarGroup, root: ToolbarRootState) {
203
- this.opts = opts;
204
- this.group = group;
205
- this.root = root;
206
- this.rovingItem = new RovingFocusItem({
207
- group: this.root.rovingFocusGroup,
208
- ref: this.opts.ref,
209
- });
210
-
211
- this.onclick = this.onclick.bind(this);
212
- this.onkeydown = this.onkeydown.bind(this);
213
- }
214
-
215
- #toggleItem() {
216
- if (this.#isDisabled) return;
217
- this.group.toggleItem(this.opts.value.current);
218
- }
219
-
220
- onclick(_: BitsMouseEvent) {
221
- if (this.#isDisabled) return;
222
- this.#toggleItem();
223
- }
224
-
225
- onkeydown(e: BitsKeyboardEvent) {
226
- if (this.#isDisabled) return;
227
- if (e.key === kbd.ENTER || e.key === kbd.SPACE) {
228
- e.preventDefault();
229
- this.#toggleItem();
230
- return;
231
- }
232
-
233
- this.rovingItem.handleKeydown(e);
234
- }
235
-
236
- readonly isPressed = $derived.by(() => this.group.includesItem(this.opts.value.current));
237
-
238
- readonly #ariaChecked = $derived.by(() => {
239
- return this.group.isMulti ? undefined : getAriaChecked(this.isPressed, false);
240
- });
241
-
242
- readonly #ariaPressed = $derived.by(() => {
243
- return this.group.isMulti ? boolToStr(this.isPressed) : undefined;
244
- });
245
-
246
- readonly props = $derived.by(
247
- () =>
248
- ({
249
- id: this.opts.id.current,
250
- role: this.group.isMulti ? undefined : 'radio',
251
- 'data-orientation': this.root.opts.orientation.current,
252
- 'data-disabled': boolToEmptyStrOrUndef(this.#isDisabled),
253
- 'data-state': getToggleItemDataState(this.isPressed),
254
- 'data-value': this.opts.value.current,
255
- 'aria-pressed': this.#ariaPressed,
256
- 'aria-checked': this.#ariaChecked,
257
- [toolbarAttrs.item]: '',
258
- [toolbarAttrs['group-item']]: '',
259
- disabled: boolToTrueOrUndef(this.#isDisabled),
260
- //
261
- onclick: this.onclick,
262
- onkeydown: this.onkeydown,
263
- ...this.rovingItem.props,
264
- }) as const,
265
- );
111
+ export function createToolbarGroupItem(opts: SelectionItemOpts) {
112
+ return new SelectionItemState(opts, getToolbarGroup());
266
113
  }
267
114
 
268
115
  interface ToolbarLinkStateOpts extends WithRefOpts {}
@@ -365,9 +212,3 @@ export class ToolbarButtonState {
365
212
  }) as const,
366
213
  );
367
214
  }
368
-
369
- //
370
- // HELPERS
371
- //
372
-
373
- const getToggleItemDataState = (condition: boolean) => condition ? 'on' : 'off';
@@ -55,9 +55,12 @@ type RovingFocusGroupOptions = (
55
55
  export class RovingFocusGroup {
56
56
  readonly #opts: RovingFocusGroupOptions;
57
57
  readonly #currentTabStopId = simpleBox<string | null>(null);
58
+ /** What a candidate of an attribute-marked group stamps on itself; empty for the other kinds. */
59
+ readonly candidateAttrs: Record<string, ''>;
58
60
 
59
61
  constructor(opts: RovingFocusGroupOptions) {
60
62
  this.#opts = opts;
63
+ this.candidateAttrs = opts.candidateAttr ? { [opts.candidateAttr]: '' } : {};
61
64
  }
62
65
 
63
66
  getCandidateNodes() {