@zag-js/tabs 0.82.2 → 1.0.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/index.mjs CHANGED
@@ -1,78 +1,67 @@
1
1
  import { createAnatomy } from '@zag-js/anatomy';
2
- import { createScope, queryAll, nextById, prevById, itemById, dataAttr, isSelfTarget, isComposingEvent, getEventKey, isSafari, clickIfLink, raf, getFocusables, nextTick, isAnchorElement } from '@zag-js/dom-query';
3
- import { first, last, createSplitProps, compact, isEqual } from '@zag-js/utils';
4
- import { createMachine, guards } from '@zag-js/core';
5
- import { trackElementRect } from '@zag-js/element-rect';
2
+ import { clickIfLink, raf, getFocusables, nextTick, isAnchorElement, nextById, prevById, itemById, dataAttr, isSelfTarget, isComposingEvent, getEventKey, isSafari, queryAll } from '@zag-js/dom-query';
3
+ import { createSplitProps, first, last } from '@zag-js/utils';
4
+ import { createGuards, createMachine } from '@zag-js/core';
6
5
  import { createProps } from '@zag-js/types';
7
6
 
8
7
  // src/tabs.anatomy.ts
9
8
  var anatomy = createAnatomy("tabs").parts("root", "list", "trigger", "content", "indicator");
10
9
  var parts = anatomy.build();
11
- var dom = createScope({
12
- getRootId: (ctx) => ctx.ids?.root ?? `tabs:${ctx.id}`,
13
- getListId: (ctx) => ctx.ids?.list ?? `tabs:${ctx.id}:list`,
14
- getContentId: (ctx, id) => ctx.ids?.content ?? `tabs:${ctx.id}:content-${id}`,
15
- getTriggerId: (ctx, id) => ctx.ids?.trigger ?? `tabs:${ctx.id}:trigger-${id}`,
16
- getIndicatorId: (ctx) => ctx.ids?.indicator ?? `tabs:${ctx.id}:indicator`,
17
- getListEl: (ctx) => dom.getById(ctx, dom.getListId(ctx)),
18
- getContentEl: (ctx, id) => dom.getById(ctx, dom.getContentId(ctx, id)),
19
- getTriggerEl: (ctx, id) => dom.getById(ctx, dom.getTriggerId(ctx, id)),
20
- getIndicatorEl: (ctx) => dom.getById(ctx, dom.getIndicatorId(ctx)),
21
- getElements: (ctx) => {
22
- const ownerId = CSS.escape(dom.getListId(ctx));
23
- const selector = `[role=tab][data-ownedby='${ownerId}']:not([disabled])`;
24
- return queryAll(dom.getListEl(ctx), selector);
25
- },
26
- getFirstTriggerEl: (ctx) => first(dom.getElements(ctx)),
27
- getLastTriggerEl: (ctx) => last(dom.getElements(ctx)),
28
- getNextTriggerEl: (ctx, id) => nextById(dom.getElements(ctx), dom.getTriggerId(ctx, id), ctx.loopFocus),
29
- getPrevTriggerEl: (ctx, id) => prevById(dom.getElements(ctx), dom.getTriggerId(ctx, id), ctx.loopFocus),
30
- getSelectedContentEl: (ctx) => {
31
- if (!ctx.value) return;
32
- return dom.getContentEl(ctx, ctx.value);
33
- },
34
- getSelectedTriggerEl: (ctx) => {
35
- if (!ctx.value) return;
36
- return dom.getTriggerEl(ctx, ctx.value);
37
- },
38
- getOffsetRect: (el) => {
39
- return {
40
- left: el?.offsetLeft ?? 0,
41
- top: el?.offsetTop ?? 0,
42
- width: el?.offsetWidth ?? 0,
43
- height: el?.offsetHeight ?? 0
44
- };
45
- },
46
- getRectById: (ctx, id) => {
47
- const tab = itemById(dom.getElements(ctx), dom.getTriggerId(ctx, id));
48
- return dom.resolveRect(dom.getOffsetRect(tab));
49
- },
50
- resolveRect: (rect) => ({
51
- width: `${rect.width}px`,
52
- height: `${rect.height}px`,
53
- left: `${rect.left}px`,
54
- top: `${rect.top}px`
55
- })
10
+ var getRootId = (ctx) => ctx.ids?.root ?? `tabs:${ctx.id}`;
11
+ var getListId = (ctx) => ctx.ids?.list ?? `tabs:${ctx.id}:list`;
12
+ var getContentId = (ctx, id) => ctx.ids?.content ?? `tabs:${ctx.id}:content-${id}`;
13
+ var getTriggerId = (ctx, id) => ctx.ids?.trigger ?? `tabs:${ctx.id}:trigger-${id}`;
14
+ var getIndicatorId = (ctx) => ctx.ids?.indicator ?? `tabs:${ctx.id}:indicator`;
15
+ var getListEl = (ctx) => ctx.getById(getListId(ctx));
16
+ var getContentEl = (ctx, id) => ctx.getById(getContentId(ctx, id));
17
+ var getTriggerEl = (ctx, id) => ctx.getById(getTriggerId(ctx, id));
18
+ var getIndicatorEl = (ctx) => ctx.getById(getIndicatorId(ctx));
19
+ var getElements = (ctx) => {
20
+ const ownerId = CSS.escape(getListId(ctx));
21
+ const selector = `[role=tab][data-ownedby='${ownerId}']:not([disabled])`;
22
+ return queryAll(getListEl(ctx), selector);
23
+ };
24
+ var getFirstTriggerEl = (ctx) => first(getElements(ctx));
25
+ var getLastTriggerEl = (ctx) => last(getElements(ctx));
26
+ var getNextTriggerEl = (ctx, opts) => nextById(getElements(ctx), getTriggerId(ctx, opts.value), opts.loopFocus);
27
+ var getPrevTriggerEl = (ctx, opts) => prevById(getElements(ctx), getTriggerId(ctx, opts.value), opts.loopFocus);
28
+ var getOffsetRect = (el) => {
29
+ return {
30
+ left: el?.offsetLeft ?? 0,
31
+ top: el?.offsetTop ?? 0,
32
+ width: el?.offsetWidth ?? 0,
33
+ height: el?.offsetHeight ?? 0
34
+ };
35
+ };
36
+ var getRectById = (ctx, id) => {
37
+ const tab = itemById(getElements(ctx), getTriggerId(ctx, id));
38
+ return resolveRect(getOffsetRect(tab));
39
+ };
40
+ var resolveRect = (rect) => ({
41
+ width: `${rect.width}px`,
42
+ height: `${rect.height}px`,
43
+ left: `${rect.left}px`,
44
+ top: `${rect.top}px`
56
45
  });
57
46
 
58
47
  // src/tabs.connect.ts
59
- function connect(state, send, normalize) {
60
- const translations = state.context.translations;
48
+ function connect(service, normalize) {
49
+ const { state, send, context, prop, scope } = service;
50
+ const translations = prop("translations");
61
51
  const focused = state.matches("focused");
62
- const isVertical = state.context.orientation === "vertical";
63
- const isHorizontal = state.context.orientation === "horizontal";
64
- const composite = state.context.composite;
65
- const indicator = state.context.indicatorState;
52
+ const isVertical = prop("orientation") === "vertical";
53
+ const isHorizontal = prop("orientation") === "horizontal";
54
+ const composite = prop("composite");
66
55
  function getTriggerState(props2) {
67
56
  return {
68
- selected: state.context.value === props2.value,
69
- focused: state.context.focusedValue === props2.value,
57
+ selected: context.get("value") === props2.value,
58
+ focused: context.get("focusedValue") === props2.value,
70
59
  disabled: !!props2.disabled
71
60
  };
72
61
  }
73
62
  return {
74
- value: state.context.value,
75
- focusedValue: state.context.focusedValue,
63
+ value: context.get("value"),
64
+ focusedValue: context.get("focusedValue"),
76
65
  setValue(value) {
77
66
  send({ type: "SET_VALUE", value });
78
67
  },
@@ -80,11 +69,11 @@ function connect(state, send, normalize) {
80
69
  send({ type: "CLEAR_VALUE" });
81
70
  },
82
71
  setIndicatorRect(value) {
83
- const id = dom.getTriggerId(state.context, value);
72
+ const id = getTriggerId(scope, value);
84
73
  send({ type: "SET_INDICATOR_RECT", id });
85
74
  },
86
75
  syncTabIndex() {
87
- send("SYNC_TAB_INDEX");
76
+ send({ type: "SYNC_TAB_INDEX" });
88
77
  },
89
78
  selectNext(fromValue) {
90
79
  send({ type: "TAB_FOCUS", value: fromValue, src: "selectNext" });
@@ -95,26 +84,28 @@ function connect(state, send, normalize) {
95
84
  send({ type: "ARROW_PREV", src: "selectPrev" });
96
85
  },
97
86
  focus() {
98
- dom.getSelectedTriggerEl(state.context)?.focus();
87
+ const value = context.get("value");
88
+ if (!value) return;
89
+ getTriggerEl(scope, value)?.focus();
99
90
  },
100
91
  getRootProps() {
101
92
  return normalize.element({
102
93
  ...parts.root.attrs,
103
- id: dom.getRootId(state.context),
104
- "data-orientation": state.context.orientation,
94
+ id: getRootId(scope),
95
+ "data-orientation": prop("orientation"),
105
96
  "data-focus": dataAttr(focused),
106
- dir: state.context.dir
97
+ dir: prop("dir")
107
98
  });
108
99
  },
109
100
  getListProps() {
110
101
  return normalize.element({
111
102
  ...parts.list.attrs,
112
- id: dom.getListId(state.context),
103
+ id: getListId(scope),
113
104
  role: "tablist",
114
- dir: state.context.dir,
105
+ dir: prop("dir"),
115
106
  "data-focus": dataAttr(focused),
116
- "aria-orientation": state.context.orientation,
117
- "data-orientation": state.context.orientation,
107
+ "aria-orientation": prop("orientation"),
108
+ "data-orientation": prop("orientation"),
118
109
  "aria-label": translations?.listLabel,
119
110
  onKeyDown(event) {
120
111
  if (event.defaultPrevented) return;
@@ -138,16 +129,19 @@ function connect(state, send, normalize) {
138
129
  send({ type: "ARROW_NEXT", key: "ArrowRight" });
139
130
  },
140
131
  Home() {
141
- send("HOME");
132
+ send({ type: "HOME" });
142
133
  },
143
134
  End() {
144
- send("END");
135
+ send({ type: "END" });
145
136
  },
146
137
  Enter() {
147
138
  send({ type: "ENTER" });
148
139
  }
149
140
  };
150
- let key = getEventKey(event, state.context);
141
+ let key = getEventKey(event, {
142
+ dir: prop("dir"),
143
+ orientation: prop("orientation")
144
+ });
151
145
  const exec = keyMap[key];
152
146
  if (exec) {
153
147
  event.preventDefault();
@@ -165,18 +159,18 @@ function connect(state, send, normalize) {
165
159
  role: "tab",
166
160
  type: "button",
167
161
  disabled,
168
- dir: state.context.dir,
169
- "data-orientation": state.context.orientation,
162
+ dir: prop("dir"),
163
+ "data-orientation": prop("orientation"),
170
164
  "data-disabled": dataAttr(disabled),
171
165
  "aria-disabled": disabled,
172
166
  "data-value": value,
173
167
  "aria-selected": triggerState.selected,
174
168
  "data-selected": dataAttr(triggerState.selected),
175
169
  "data-focus": dataAttr(triggerState.focused),
176
- "aria-controls": triggerState.selected ? dom.getContentId(state.context, value) : void 0,
177
- "data-ownedby": dom.getListId(state.context),
178
- "data-ssr": dataAttr(state.context.ssr),
179
- id: dom.getTriggerId(state.context, value),
170
+ "aria-controls": triggerState.selected ? getContentId(scope, value) : void 0,
171
+ "data-ownedby": getListId(scope),
172
+ "data-ssr": dataAttr(context.get("ssr")),
173
+ id: getTriggerId(scope, value),
180
174
  tabIndex: triggerState.selected && composite ? 0 : -1,
181
175
  onFocus() {
182
176
  send({ type: "TAB_FOCUS", value });
@@ -199,36 +193,38 @@ function connect(state, send, normalize) {
199
193
  },
200
194
  getContentProps(props2) {
201
195
  const { value } = props2;
202
- const selected = state.context.value === value;
196
+ const selected = context.get("value") === value;
203
197
  return normalize.element({
204
198
  ...parts.content.attrs,
205
- dir: state.context.dir,
206
- id: dom.getContentId(state.context, value),
199
+ dir: prop("dir"),
200
+ id: getContentId(scope, value),
207
201
  tabIndex: composite ? 0 : -1,
208
- "aria-labelledby": dom.getTriggerId(state.context, value),
202
+ "aria-labelledby": getTriggerId(scope, value),
209
203
  role: "tabpanel",
210
- "data-ownedby": dom.getListId(state.context),
204
+ "data-ownedby": getListId(scope),
211
205
  "data-selected": dataAttr(selected),
212
- "data-orientation": state.context.orientation,
206
+ "data-orientation": prop("orientation"),
213
207
  hidden: !selected
214
208
  });
215
209
  },
216
210
  getIndicatorProps() {
211
+ const indicatorRect = context.get("indicatorRect");
212
+ const indicatorTransition = context.get("indicatorTransition");
217
213
  return normalize.element({
218
- id: dom.getIndicatorId(state.context),
214
+ id: getIndicatorId(scope),
219
215
  ...parts.indicator.attrs,
220
- dir: state.context.dir,
221
- "data-orientation": state.context.orientation,
216
+ dir: prop("dir"),
217
+ "data-orientation": prop("orientation"),
222
218
  style: {
223
219
  "--transition-property": "left, right, top, bottom, width, height",
224
- "--left": indicator.rect?.left,
225
- "--top": indicator.rect?.top,
226
- "--width": indicator.rect?.width,
227
- "--height": indicator.rect?.height,
220
+ "--left": indicatorRect.left,
221
+ "--top": indicatorRect.top,
222
+ "--width": indicatorRect.width,
223
+ "--height": indicatorRect.height,
228
224
  position: "absolute",
229
225
  willChange: "var(--transition-property)",
230
226
  transitionProperty: "var(--transition-property)",
231
- transitionDuration: indicator.transition ? "var(--transition-duration, 150ms)" : "0ms",
227
+ transitionDuration: indicatorTransition ? "var(--transition-duration, 150ms)" : "0ms",
232
228
  transitionTimingFunction: "var(--transition-timing-function)",
233
229
  [isHorizontal ? "left" : "top"]: isHorizontal ? "var(--left)" : "var(--top)"
234
230
  }
@@ -236,270 +232,276 @@ function connect(state, send, normalize) {
236
232
  }
237
233
  };
238
234
  }
239
- var { not } = guards;
240
- function machine(userContext) {
241
- const ctx = compact(userContext);
242
- return createMachine(
243
- {
244
- initial: "idle",
245
- context: {
246
- dir: "ltr",
247
- orientation: "horizontal",
248
- activationMode: "automatic",
249
- value: null,
250
- loopFocus: true,
251
- composite: true,
252
- navigate(details) {
253
- clickIfLink(details.node);
235
+ var { not } = createGuards();
236
+ var machine = createMachine({
237
+ props({ props: props2 }) {
238
+ return {
239
+ dir: "ltr",
240
+ orientation: "horizontal",
241
+ activationMode: "automatic",
242
+ loopFocus: true,
243
+ composite: true,
244
+ navigate(details) {
245
+ clickIfLink(details.node);
246
+ },
247
+ defaultValue: null,
248
+ ...props2
249
+ };
250
+ },
251
+ initialState() {
252
+ return "idle";
253
+ },
254
+ context({ prop, bindable }) {
255
+ return {
256
+ value: bindable(() => ({
257
+ defaultValue: prop("defaultValue"),
258
+ value: prop("value"),
259
+ onChange(value) {
260
+ prop("onValueChange")?.({ value });
261
+ }
262
+ })),
263
+ focusedValue: bindable(() => ({
264
+ defaultValue: prop("value") || prop("defaultValue"),
265
+ onChange(value) {
266
+ prop("onFocusChange")?.({ focusedValue: value });
267
+ }
268
+ })),
269
+ ssr: bindable(() => ({ defaultValue: true })),
270
+ indicatorTransition: bindable(() => ({ defaultValue: false })),
271
+ indicatorRect: bindable(() => ({
272
+ defaultValue: { left: "0px", top: "0px", width: "0px", height: "0px" }
273
+ }))
274
+ };
275
+ },
276
+ watch({ context, prop, track, action }) {
277
+ track([() => context.get("value")], () => {
278
+ action(["allowIndicatorTransition", "syncIndicatorRect", "syncTabIndex", "navigateIfNeeded"]);
279
+ });
280
+ track([() => prop("dir"), () => prop("orientation")], () => {
281
+ action(["syncIndicatorRect"]);
282
+ });
283
+ },
284
+ on: {
285
+ SET_VALUE: {
286
+ actions: ["setValue"]
287
+ },
288
+ CLEAR_VALUE: {
289
+ actions: ["clearValue"]
290
+ },
291
+ SET_INDICATOR_RECT: {
292
+ actions: ["setIndicatorRect"]
293
+ },
294
+ SYNC_TAB_INDEX: {
295
+ actions: ["syncTabIndex"]
296
+ }
297
+ },
298
+ entry: ["syncIndicatorRect", "syncTabIndex", "syncSsr"],
299
+ exit: ["cleanupObserver"],
300
+ states: {
301
+ idle: {
302
+ on: {
303
+ TAB_FOCUS: {
304
+ target: "focused",
305
+ actions: ["setFocusedValue"]
254
306
  },
255
- ...ctx,
256
- focusedValue: ctx.value ?? null,
257
- ssr: true,
258
- indicatorState: {
259
- rendered: false,
260
- transition: false,
261
- rect: { left: "0px", top: "0px", width: "0px", height: "0px" }
307
+ TAB_CLICK: {
308
+ target: "focused",
309
+ actions: ["setFocusedValue", "setValue"]
262
310
  }
263
- },
264
- watch: {
265
- value: ["allowIndicatorTransition", "syncIndicatorRect", "syncTabIndex", "navigateIfNeeded"],
266
- dir: ["syncIndicatorRect"],
267
- orientation: ["syncIndicatorRect"]
268
- },
311
+ }
312
+ },
313
+ focused: {
269
314
  on: {
270
- SET_VALUE: {
271
- actions: "setValue"
315
+ TAB_CLICK: {
316
+ target: "focused",
317
+ actions: ["setFocusedValue", "setValue"]
272
318
  },
273
- CLEAR_VALUE: {
274
- actions: "clearValue"
319
+ ARROW_PREV: [
320
+ {
321
+ guard: "selectOnFocus",
322
+ actions: ["focusPrevTab", "selectFocusedTab"]
323
+ },
324
+ {
325
+ actions: ["focusPrevTab"]
326
+ }
327
+ ],
328
+ ARROW_NEXT: [
329
+ {
330
+ guard: "selectOnFocus",
331
+ actions: ["focusNextTab", "selectFocusedTab"]
332
+ },
333
+ {
334
+ actions: ["focusNextTab"]
335
+ }
336
+ ],
337
+ HOME: [
338
+ {
339
+ guard: "selectOnFocus",
340
+ actions: ["focusFirstTab", "selectFocusedTab"]
341
+ },
342
+ {
343
+ actions: ["focusFirstTab"]
344
+ }
345
+ ],
346
+ END: [
347
+ {
348
+ guard: "selectOnFocus",
349
+ actions: ["focusLastTab", "selectFocusedTab"]
350
+ },
351
+ {
352
+ actions: ["focusLastTab"]
353
+ }
354
+ ],
355
+ ENTER: {
356
+ guard: not("selectOnFocus"),
357
+ actions: ["selectFocusedTab"]
275
358
  },
276
- SET_INDICATOR_RECT: {
277
- actions: "setIndicatorRect"
359
+ TAB_FOCUS: {
360
+ actions: ["setFocusedValue"]
278
361
  },
279
- SYNC_TAB_INDEX: {
280
- actions: "syncTabIndex"
362
+ TAB_BLUR: {
363
+ target: "idle",
364
+ actions: ["clearFocusedValue"]
281
365
  }
366
+ }
367
+ }
368
+ },
369
+ implementations: {
370
+ guards: {
371
+ selectOnFocus: ({ prop }) => prop("activationMode") === "automatic"
372
+ },
373
+ actions: {
374
+ selectFocusedTab({ context, prop }) {
375
+ raf(() => {
376
+ const focusedValue = context.get("focusedValue");
377
+ if (!focusedValue) return;
378
+ const nullable = prop("deselectable") && context.get("value") === focusedValue;
379
+ const value = nullable ? null : focusedValue;
380
+ context.set("value", value);
381
+ });
282
382
  },
283
- created: ["syncFocusedValue"],
284
- entry: ["checkRenderedElements", "syncIndicatorRect", "syncTabIndex", "syncSsr"],
285
- exit: ["cleanupObserver"],
286
- states: {
287
- idle: {
288
- on: {
289
- TAB_FOCUS: {
290
- target: "focused",
291
- actions: "setFocusedValue"
292
- },
293
- TAB_CLICK: {
294
- target: "focused",
295
- actions: ["setFocusedValue", "setValue"]
296
- }
383
+ setFocusedValue({ context, event, flush }) {
384
+ if (event.value == null) return;
385
+ flush(() => {
386
+ context.set("focusedValue", event.value);
387
+ });
388
+ },
389
+ clearFocusedValue({ context }) {
390
+ context.set("focusedValue", null);
391
+ },
392
+ setValue({ context, event, prop }) {
393
+ const nullable = prop("deselectable") && context.get("value") === context.get("focusedValue");
394
+ context.set("value", nullable ? null : event.value);
395
+ },
396
+ clearValue({ context }) {
397
+ context.set("value", null);
398
+ },
399
+ focusFirstTab({ scope }) {
400
+ raf(() => {
401
+ getFirstTriggerEl(scope)?.focus();
402
+ });
403
+ },
404
+ focusLastTab({ scope }) {
405
+ raf(() => {
406
+ getLastTriggerEl(scope)?.focus();
407
+ });
408
+ },
409
+ focusNextTab({ context, prop, scope, event }) {
410
+ const focusedValue = event.value ?? context.get("focusedValue");
411
+ if (!focusedValue) return;
412
+ const triggerEl = getNextTriggerEl(scope, {
413
+ value: focusedValue,
414
+ loopFocus: prop("loopFocus")
415
+ });
416
+ raf(() => {
417
+ if (prop("composite")) {
418
+ triggerEl?.focus();
419
+ } else if (triggerEl?.dataset.value != null) {
420
+ context.set("focusedValue", triggerEl.dataset.value);
297
421
  }
298
- },
299
- focused: {
300
- on: {
301
- TAB_CLICK: {
302
- target: "focused",
303
- actions: ["setFocusedValue", "setValue"]
304
- },
305
- ARROW_PREV: [
306
- {
307
- guard: "selectOnFocus",
308
- actions: ["focusPrevTab", "selectFocusedTab"]
309
- },
310
- {
311
- actions: "focusPrevTab"
312
- }
313
- ],
314
- ARROW_NEXT: [
315
- {
316
- guard: "selectOnFocus",
317
- actions: ["focusNextTab", "selectFocusedTab"]
318
- },
319
- {
320
- actions: "focusNextTab"
321
- }
322
- ],
323
- HOME: [
324
- {
325
- guard: "selectOnFocus",
326
- actions: ["focusFirstTab", "selectFocusedTab"]
327
- },
328
- {
329
- actions: "focusFirstTab"
330
- }
331
- ],
332
- END: [
333
- {
334
- guard: "selectOnFocus",
335
- actions: ["focusLastTab", "selectFocusedTab"]
336
- },
337
- {
338
- actions: "focusLastTab"
339
- }
340
- ],
341
- ENTER: {
342
- guard: not("selectOnFocus"),
343
- actions: "selectFocusedTab"
344
- },
345
- TAB_FOCUS: {
346
- actions: ["setFocusedValue"]
347
- },
348
- TAB_BLUR: {
349
- target: "idle",
350
- actions: "clearFocusedValue"
351
- }
422
+ });
423
+ },
424
+ focusPrevTab({ context, prop, scope, event }) {
425
+ const focusedValue = event.value ?? context.get("focusedValue");
426
+ if (!focusedValue) return;
427
+ const triggerEl = getPrevTriggerEl(scope, {
428
+ value: focusedValue,
429
+ loopFocus: prop("loopFocus")
430
+ });
431
+ raf(() => {
432
+ if (prop("composite")) {
433
+ triggerEl?.focus();
434
+ } else if (triggerEl?.dataset.value != null) {
435
+ context.set("focusedValue", triggerEl.dataset.value);
352
436
  }
353
- }
354
- }
355
- },
356
- {
357
- guards: {
358
- selectOnFocus: (ctx2) => ctx2.activationMode === "automatic"
437
+ });
359
438
  },
360
- actions: {
361
- syncFocusedValue(ctx2) {
362
- if (ctx2.value != null && ctx2.focusedValue == null) {
363
- ctx2.focusedValue = ctx2.value;
439
+ syncTabIndex({ context, scope }) {
440
+ raf(() => {
441
+ const value = context.get("value");
442
+ if (!value) return;
443
+ const contentEl = getContentEl(scope, value);
444
+ if (!contentEl) return;
445
+ const focusables = getFocusables(contentEl);
446
+ if (focusables.length > 0) {
447
+ contentEl.removeAttribute("tabindex");
448
+ } else {
449
+ contentEl.setAttribute("tabindex", "0");
364
450
  }
365
- },
366
- selectFocusedTab(ctx2) {
367
- raf(() => {
368
- const nullable = ctx2.deselectable && ctx2.value === ctx2.focusedValue;
369
- const value = nullable ? null : ctx2.focusedValue;
370
- set.value(ctx2, value);
371
- });
372
- },
373
- setFocusedValue(ctx2, evt) {
374
- if (evt.value == null) return;
375
- set.focusedValue(ctx2, evt.value);
376
- },
377
- clearFocusedValue(ctx2) {
378
- set.focusedValue(ctx2, null);
379
- },
380
- setValue(ctx2, evt) {
381
- const nullable = ctx2.deselectable && ctx2.value === ctx2.focusedValue;
382
- const value = nullable ? null : evt.value;
383
- set.value(ctx2, value);
384
- },
385
- clearValue(ctx2) {
386
- set.value(ctx2, null);
387
- },
388
- focusFirstTab(ctx2) {
389
- raf(() => {
390
- dom.getFirstTriggerEl(ctx2)?.focus();
391
- });
392
- },
393
- focusLastTab(ctx2) {
394
- raf(() => {
395
- dom.getLastTriggerEl(ctx2)?.focus();
396
- });
397
- },
398
- focusNextTab(ctx2) {
399
- if (!ctx2.focusedValue) return;
400
- const triggerEl = dom.getNextTriggerEl(ctx2, ctx2.focusedValue);
401
- raf(() => {
402
- if (ctx2.composite) {
403
- triggerEl?.focus();
404
- } else if (triggerEl?.dataset.value != null) {
405
- set.focusedValue(ctx2, triggerEl.dataset.value);
406
- }
407
- });
408
- },
409
- focusPrevTab(ctx2) {
410
- if (!ctx2.focusedValue) return;
411
- const triggerEl = dom.getPrevTriggerEl(ctx2, ctx2.focusedValue);
412
- raf(() => {
413
- if (ctx2.composite) {
414
- triggerEl?.focus();
415
- } else if (triggerEl?.dataset.value != null) {
416
- set.focusedValue(ctx2, triggerEl.dataset.value);
417
- }
418
- });
419
- },
420
- checkRenderedElements(ctx2) {
421
- ctx2.indicatorState.rendered = !!dom.getIndicatorEl(ctx2);
422
- },
423
- syncTabIndex(ctx2) {
424
- raf(() => {
425
- const contentEl = dom.getSelectedContentEl(ctx2);
426
- if (!contentEl) return;
427
- const focusables = getFocusables(contentEl);
428
- if (focusables.length > 0) {
429
- contentEl.removeAttribute("tabindex");
430
- } else {
431
- contentEl.setAttribute("tabindex", "0");
432
- }
433
- });
434
- },
435
- cleanupObserver(ctx2) {
436
- ctx2.indicatorCleanup?.();
437
- },
438
- allowIndicatorTransition(ctx2) {
439
- ctx2.indicatorState.transition = true;
440
- },
441
- setIndicatorRect(ctx2, evt) {
442
- const value = evt.id ?? ctx2.value;
443
- if (!ctx2.indicatorState.rendered || !value) return;
444
- const triggerEl = dom.getTriggerEl(ctx2, value);
445
- if (!triggerEl) return;
446
- ctx2.indicatorState.rect = dom.getRectById(ctx2, value);
451
+ });
452
+ },
453
+ cleanupObserver({ refs }) {
454
+ const cleanup = refs.get("indicatorCleanup");
455
+ if (cleanup) cleanup();
456
+ },
457
+ allowIndicatorTransition({ context }) {
458
+ context.set("indicatorTransition", true);
459
+ },
460
+ setIndicatorRect({ context, event, scope }) {
461
+ const value = event.id ?? context.get("value");
462
+ const indicatorEl = getIndicatorEl(scope);
463
+ if (!indicatorEl || !value) return;
464
+ const triggerEl = getTriggerEl(scope, value);
465
+ if (!triggerEl) return;
466
+ context.set("indicatorRect", getRectById(scope, value));
467
+ nextTick(() => {
468
+ context.set("indicatorTransition", false);
469
+ });
470
+ },
471
+ syncSsr({ context }) {
472
+ context.set("ssr", false);
473
+ },
474
+ syncIndicatorRect({ context, refs, scope }) {
475
+ const cleanup = refs.get("indicatorCleanup");
476
+ if (cleanup) cleanup();
477
+ const value = context.get("value");
478
+ if (!value) return;
479
+ const triggerEl = getTriggerEl(scope, value);
480
+ const indicatorEl = getIndicatorEl(scope);
481
+ if (!triggerEl || !indicatorEl) return;
482
+ const exec = () => {
483
+ const rect = getOffsetRect(triggerEl);
484
+ context.set("indicatorRect", resolveRect(rect));
447
485
  nextTick(() => {
448
- ctx2.indicatorState.transition = false;
486
+ context.set("indicatorTransition", false);
449
487
  });
450
- },
451
- syncSsr(ctx2) {
452
- ctx2.ssr = false;
453
- },
454
- syncIndicatorRect(ctx2) {
455
- ctx2.indicatorCleanup?.();
456
- const value = ctx2.value;
457
- if (!ctx2.indicatorState.rendered || !value) return;
458
- const triggerEl = dom.getSelectedTriggerEl(ctx2);
459
- if (!triggerEl) return;
460
- ctx2.indicatorCleanup = trackElementRect(triggerEl, {
461
- getRect(el) {
462
- return dom.getOffsetRect(el);
463
- },
464
- onChange(rect) {
465
- ctx2.indicatorState.rect = dom.resolveRect(rect);
466
- nextTick(() => {
467
- ctx2.indicatorState.transition = false;
468
- });
469
- }
470
- });
471
- },
472
- navigateIfNeeded(ctx2) {
473
- const triggerEl = dom.getSelectedTriggerEl(ctx2);
474
- if (!isAnchorElement(triggerEl)) return;
475
- ctx2.navigate({ value: ctx2.value, node: triggerEl });
476
- }
488
+ };
489
+ exec();
490
+ const win = scope.getWin();
491
+ const obs = new win.ResizeObserver(exec);
492
+ obs.observe(triggerEl);
493
+ refs.set("indicatorCleanup", () => obs.disconnect());
494
+ },
495
+ navigateIfNeeded({ context, prop, scope }) {
496
+ const value = context.get("value");
497
+ if (!value) return;
498
+ const triggerEl = getTriggerEl(scope, value);
499
+ if (!isAnchorElement(triggerEl)) return;
500
+ prop("navigate")?.({ value, node: triggerEl });
477
501
  }
478
502
  }
479
- );
480
- }
481
- var invoke = {
482
- change: (ctx) => {
483
- if (ctx.value == null) return;
484
- ctx.onValueChange?.({ value: ctx.value });
485
- },
486
- focusChange: (ctx) => {
487
- if (ctx.focusedValue == null) return;
488
- ctx.onFocusChange?.({ focusedValue: ctx.focusedValue });
489
- }
490
- };
491
- var set = {
492
- value: (ctx, value) => {
493
- if (isEqual(value, ctx.value)) return;
494
- ctx.value = value;
495
- invoke.change(ctx);
496
- },
497
- focusedValue: (ctx, value) => {
498
- if (isEqual(value, ctx.focusedValue)) return;
499
- ctx.focusedValue = value;
500
- invoke.focusChange(ctx);
501
503
  }
502
- };
504
+ });
503
505
  var props = createProps()([
504
506
  "activationMode",
505
507
  "composite",
@@ -514,7 +516,8 @@ var props = createProps()([
514
516
  "onValueChange",
515
517
  "orientation",
516
518
  "translations",
517
- "value"
519
+ "value",
520
+ "defaultValue"
518
521
  ]);
519
522
  var splitProps = createSplitProps(props);
520
523
  var triggerProps = createProps()(["disabled", "value"]);