@destyler/tabs 0.0.7-beta.7 → 0.0.7-beta.9

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