@destyler/dynamic 0.0.6 → 0.0.7-beta.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/dist/index.mjs CHANGED
@@ -1,10 +1,1024 @@
1
- export { DynamicClear, DynamicInput, DynamicItem, DynamicItemDelete, DynamicItemText, DynamicRoot, dynamicClearProps, dynamicInputProps, dynamicItemDeleteProps, dynamicItemProps, dynamicItemTextProps, dynamicRootProps } from './component.mjs';
2
- import 'vue';
3
- import '@destyler/primitive';
4
- import '@destyler/composition';
5
- import '@destyler/shared';
6
- import '@vueuse/core';
7
- import '@destyler/collection';
8
- import '@destyler/collection/composition';
9
- import '@destyler/visually-hidden/component';
10
- import '@destyler/directives';
1
+ import { createAnatomy } from '@zag-js/anatomy';
2
+ import { getNativeEvent, getEventKey } from '@zag-js/dom-event';
3
+ import { createScope, queryAll, prevById, nextById, indexOfId, dataAttr, ariaAttr, isComposingEvent, contains, raf } from '@zag-js/dom-query';
4
+ import { dispatchInputValueEvent, trackFormControl } from '@zag-js/form-utils';
5
+ import { autoResizeInput } from '@zag-js/auto-resize';
6
+ import { createMachine, guards } from '@zag-js/core';
7
+ import { trackInteractOutside } from '@zag-js/interact-outside';
8
+ import { createLiveRegion } from '@zag-js/live-region';
9
+ import { compact, removeAt, warn, uniq, isEqual, createSplitProps } from '@zag-js/utils';
10
+ import { createProps } from '@zag-js/types';
11
+
12
+ const anatomy = createAnatomy("dynamic").parts(
13
+ "root",
14
+ "label",
15
+ "control",
16
+ "input",
17
+ "clearTrigger",
18
+ "item",
19
+ "itemPreview",
20
+ "itemInput",
21
+ "itemText",
22
+ "itemDeleteTrigger"
23
+ );
24
+ const parts = anatomy.build();
25
+
26
+ const dom = createScope({
27
+ getRootId: (ctx) => ctx.ids?.root ?? `dynamic:${ctx.id}`,
28
+ getInputId: (ctx) => ctx.ids?.input ?? `dynamic:${ctx.id}:input`,
29
+ getClearTriggerId: (ctx) => ctx.ids?.clearBtn ?? `dynamic:${ctx.id}:clear-btn`,
30
+ getHiddenInputId: (ctx) => ctx.ids?.hiddenInput ?? `dynamic:${ctx.id}:hidden-input`,
31
+ getLabelId: (ctx) => ctx.ids?.label ?? `dynamic:${ctx.id}:label`,
32
+ getControlId: (ctx) => ctx.ids?.control ?? `dynamic:${ctx.id}:control`,
33
+ getItemId: (ctx, opt) => ctx.ids?.item?.(opt) ?? `dynamic:${ctx.id}:tag:${opt.value}:${opt.index}`,
34
+ getItemDeleteTriggerId: (ctx, opt) => ctx.ids?.itemDeleteTrigger?.(opt) ?? `${dom.getItemId(ctx, opt)}:delete-btn`,
35
+ getItemInputId: (ctx, opt) => ctx.ids?.itemInput?.(opt) ?? `${dom.getItemId(ctx, opt)}:input`,
36
+ getEditInputId: (ctx) => `${ctx.editedTagId}:input`,
37
+ getTagInputEl: (ctx, opt) => dom.getById(ctx, dom.getItemInputId(ctx, opt)),
38
+ getRootEl: (ctx) => dom.getById(ctx, dom.getRootId(ctx)),
39
+ getInputEl: (ctx) => dom.getById(ctx, dom.getInputId(ctx)),
40
+ getHiddenInputEl: (ctx) => dom.getById(ctx, dom.getHiddenInputId(ctx)),
41
+ getEditInputEl: (ctx) => dom.getById(ctx, dom.getEditInputId(ctx)),
42
+ getTagElements: (ctx) => queryAll(dom.getRootEl(ctx), `[data-part=item-preview]:not([data-disabled])`),
43
+ getFirstEl: (ctx) => dom.getTagElements(ctx)[0],
44
+ getLastEl: (ctx) => dom.getTagElements(ctx)[dom.getTagElements(ctx).length - 1],
45
+ getPrevEl: (ctx, id) => prevById(dom.getTagElements(ctx), id, false),
46
+ getNextEl: (ctx, id) => nextById(dom.getTagElements(ctx), id, false),
47
+ getTagElAtIndex: (ctx, index) => dom.getTagElements(ctx)[index],
48
+ getIndexOfId: (ctx, id) => indexOfId(dom.getTagElements(ctx), id),
49
+ isInputFocused: (ctx) => dom.getDoc(ctx).activeElement === dom.getInputEl(ctx),
50
+ getHighlightedTagValue: (ctx) => {
51
+ if (!ctx.highlightedTagId)
52
+ return null;
53
+ const tagEl = dom.getById(ctx, ctx.highlightedTagId);
54
+ return tagEl?.dataset.value ?? null;
55
+ },
56
+ setHoverIntent: (el) => {
57
+ const tagEl = el.closest("[data-part=item-preview]");
58
+ if (!tagEl)
59
+ return;
60
+ tagEl.dataset.deleteIntent = "";
61
+ },
62
+ clearHoverIntent: (el) => {
63
+ const tagEl = el.closest("[data-part=item-preview]");
64
+ if (!tagEl)
65
+ return;
66
+ delete tagEl.dataset.deleteIntent;
67
+ },
68
+ dispatchInputEvent(ctx) {
69
+ const inputEl = dom.getHiddenInputEl(ctx);
70
+ if (!inputEl)
71
+ return;
72
+ dispatchInputValueEvent(inputEl, { value: ctx.valueAsString });
73
+ }
74
+ });
75
+
76
+ function connect(state, send, normalize) {
77
+ const interactive = state.context.isInteractive;
78
+ const disabled = state.context.disabled;
79
+ const readOnly = state.context.readOnly;
80
+ const invalid = state.context.invalid || state.context.isOverflowing;
81
+ const translations = state.context.translations;
82
+ const focused = state.hasTag("focused");
83
+ const editingTag = state.matches("editing:tag");
84
+ const empty = state.context.count === 0;
85
+ function getItemState(options) {
86
+ const id = dom.getItemId(state.context, options);
87
+ return {
88
+ id,
89
+ editing: editingTag && state.context.editedTagId === id,
90
+ highlighted: id === state.context.highlightedTagId,
91
+ disabled: Boolean(options.disabled || disabled)
92
+ };
93
+ }
94
+ return {
95
+ empty,
96
+ inputValue: state.context.trimmedInputValue,
97
+ value: state.context.value,
98
+ valueAsString: state.context.valueAsString,
99
+ count: state.context.count,
100
+ atMax: state.context.isAtMax,
101
+ setValue(value) {
102
+ send({ type: "SET_VALUE", value });
103
+ },
104
+ clearValue(id) {
105
+ if (id) {
106
+ send({ type: "CLEAR_TAG", id });
107
+ } else {
108
+ send("CLEAR_VALUE");
109
+ }
110
+ },
111
+ addValue(value) {
112
+ send({ type: "ADD_TAG", value });
113
+ },
114
+ setValueAtIndex(index, value) {
115
+ send({ type: "SET_VALUE_AT_INDEX", index, value });
116
+ },
117
+ setInputValue(value) {
118
+ send({ type: "SET_INPUT_VALUE", value });
119
+ },
120
+ clearInputValue() {
121
+ send({ type: "SET_INPUT_VALUE", value: "" });
122
+ },
123
+ focus() {
124
+ dom.getInputEl(state.context)?.focus();
125
+ },
126
+ getItemState,
127
+ getRootProps() {
128
+ return normalize.element({
129
+ "dir": state.context.dir,
130
+ ...parts.root.attrs,
131
+ "data-invalid": dataAttr(invalid),
132
+ "data-readonly": dataAttr(readOnly),
133
+ "data-disabled": dataAttr(disabled),
134
+ "data-focus": dataAttr(focused),
135
+ "data-empty": dataAttr(empty),
136
+ "id": dom.getRootId(state.context),
137
+ onPointerDown() {
138
+ if (!interactive)
139
+ return;
140
+ send("POINTER_DOWN");
141
+ }
142
+ });
143
+ },
144
+ getLabelProps() {
145
+ return normalize.label({
146
+ ...parts.label.attrs,
147
+ "data-disabled": dataAttr(disabled),
148
+ "data-invalid": dataAttr(invalid),
149
+ "data-readonly": dataAttr(readOnly),
150
+ "id": dom.getLabelId(state.context),
151
+ "dir": state.context.dir,
152
+ "htmlFor": dom.getInputId(state.context)
153
+ });
154
+ },
155
+ getControlProps() {
156
+ return normalize.element({
157
+ "id": dom.getControlId(state.context),
158
+ ...parts.control.attrs,
159
+ "dir": state.context.dir,
160
+ "tabIndex": readOnly ? 0 : void 0,
161
+ "data-disabled": dataAttr(disabled),
162
+ "data-readonly": dataAttr(readOnly),
163
+ "data-invalid": dataAttr(invalid),
164
+ "data-focus": dataAttr(focused)
165
+ });
166
+ },
167
+ getInputProps() {
168
+ return normalize.input({
169
+ ...parts.input.attrs,
170
+ "dir": state.context.dir,
171
+ "data-invalid": dataAttr(invalid),
172
+ "aria-invalid": ariaAttr(invalid),
173
+ "data-readonly": dataAttr(readOnly),
174
+ "maxLength": state.context.maxLength,
175
+ "id": dom.getInputId(state.context),
176
+ "defaultValue": state.context.inputValue,
177
+ "autoComplete": "off",
178
+ "autoCorrect": "off",
179
+ "autoCapitalize": "none",
180
+ "disabled": disabled || readOnly,
181
+ onChange(event) {
182
+ const evt = getNativeEvent(event);
183
+ const value = event.currentTarget.value;
184
+ if (evt.inputType === "insertFromPaste") {
185
+ send({ type: "PASTE", value });
186
+ return;
187
+ }
188
+ if (endsWith(value, state.context.delimiter)) {
189
+ send("DELIMITER_KEY");
190
+ return;
191
+ }
192
+ send({ type: "TYPE", value, key: evt.inputType });
193
+ },
194
+ onFocus() {
195
+ send("FOCUS");
196
+ },
197
+ onKeyDown(event) {
198
+ if (event.defaultPrevented)
199
+ return;
200
+ if (isComposingEvent(event))
201
+ return;
202
+ const target = event.currentTarget;
203
+ const isCombobox = target.getAttribute("role") === "combobox";
204
+ const isExpanded = target.ariaExpanded === "true";
205
+ const keyMap = {
206
+ ArrowDown() {
207
+ send("ARROW_DOWN");
208
+ },
209
+ ArrowLeft() {
210
+ if (isCombobox && isExpanded)
211
+ return;
212
+ send("ARROW_LEFT");
213
+ },
214
+ ArrowRight(event2) {
215
+ if (state.context.highlightedTagId) {
216
+ event2.preventDefault();
217
+ }
218
+ if (isCombobox && isExpanded)
219
+ return;
220
+ send("ARROW_RIGHT");
221
+ },
222
+ Escape(event2) {
223
+ event2.preventDefault();
224
+ send("ESCAPE");
225
+ },
226
+ Backspace() {
227
+ send("BACKSPACE");
228
+ },
229
+ Delete() {
230
+ send("DELETE");
231
+ },
232
+ Enter(event2) {
233
+ if (isCombobox && isExpanded)
234
+ return;
235
+ send("ENTER");
236
+ event2.preventDefault();
237
+ }
238
+ };
239
+ const key = getEventKey(event, state.context);
240
+ const exec = keyMap[key];
241
+ if (exec) {
242
+ exec(event);
243
+ }
244
+ }
245
+ });
246
+ },
247
+ getHiddenInputProps() {
248
+ return normalize.input({
249
+ type: "text",
250
+ hidden: true,
251
+ name: state.context.name,
252
+ form: state.context.form,
253
+ disabled,
254
+ readOnly,
255
+ required: state.context.required,
256
+ id: dom.getHiddenInputId(state.context),
257
+ defaultValue: state.context.valueAsString
258
+ });
259
+ },
260
+ getItemProps(props) {
261
+ return normalize.element({
262
+ ...parts.item.attrs,
263
+ "dir": state.context.dir,
264
+ "data-value": props.value,
265
+ "data-disabled": dataAttr(disabled)
266
+ });
267
+ },
268
+ getItemPreviewProps(props) {
269
+ const itemState = getItemState(props);
270
+ return normalize.element({
271
+ ...parts.itemPreview.attrs,
272
+ "id": itemState.id,
273
+ "dir": state.context.dir,
274
+ "hidden": itemState.editing,
275
+ "data-value": props.value,
276
+ "data-disabled": dataAttr(disabled),
277
+ "data-highlighted": dataAttr(itemState.highlighted),
278
+ onPointerDown(event) {
279
+ if (!interactive || itemState.disabled)
280
+ return;
281
+ event.preventDefault();
282
+ send({ type: "POINTER_DOWN_TAG", id: itemState.id });
283
+ },
284
+ onDoubleClick() {
285
+ if (!interactive || itemState.disabled)
286
+ return;
287
+ send({ type: "DOUBLE_CLICK_TAG", id: itemState.id });
288
+ }
289
+ });
290
+ },
291
+ getItemTextProps(props) {
292
+ const itemState = getItemState(props);
293
+ return normalize.element({
294
+ ...parts.itemText.attrs,
295
+ "dir": state.context.dir,
296
+ "data-disabled": dataAttr(disabled),
297
+ "data-highlighted": dataAttr(itemState.highlighted)
298
+ });
299
+ },
300
+ getItemInputProps(props) {
301
+ const itemState = getItemState(props);
302
+ return normalize.input({
303
+ ...parts.itemInput.attrs,
304
+ "dir": state.context.dir,
305
+ "aria-label": translations.tagEdited(props.value),
306
+ "aria-hidden": true,
307
+ "disabled": disabled,
308
+ "id": dom.getItemInputId(state.context, props),
309
+ "tabIndex": -1,
310
+ "hidden": !itemState.editing,
311
+ "defaultValue": itemState.editing ? state.context.editedTagValue : "",
312
+ onChange(event) {
313
+ send({ type: "TAG_INPUT_TYPE", value: event.currentTarget.value });
314
+ },
315
+ onBlur(event) {
316
+ send({ type: "TAG_INPUT_BLUR", target: event.relatedTarget, id: itemState.id });
317
+ },
318
+ onKeyDown(event) {
319
+ if (event.defaultPrevented)
320
+ return;
321
+ if (isComposingEvent(event))
322
+ return;
323
+ const keyMap = {
324
+ Enter() {
325
+ send("TAG_INPUT_ENTER");
326
+ },
327
+ Escape() {
328
+ send("TAG_INPUT_ESCAPE");
329
+ }
330
+ };
331
+ const exec = keyMap[event.key];
332
+ if (exec) {
333
+ event.preventDefault();
334
+ exec(event);
335
+ }
336
+ }
337
+ });
338
+ },
339
+ getItemDeleteTriggerProps(props) {
340
+ const id = dom.getItemId(state.context, props);
341
+ return normalize.button({
342
+ ...parts.itemDeleteTrigger.attrs,
343
+ "dir": state.context.dir,
344
+ "id": dom.getItemDeleteTriggerId(state.context, props),
345
+ "type": "button",
346
+ "disabled": disabled,
347
+ "aria-label": translations.deleteTagTriggerLabel(props.value),
348
+ "tabIndex": -1,
349
+ onPointerDown(event) {
350
+ if (!interactive) {
351
+ event.preventDefault();
352
+ }
353
+ },
354
+ onPointerMove(event) {
355
+ if (!interactive)
356
+ return;
357
+ dom.setHoverIntent(event.currentTarget);
358
+ },
359
+ onPointerLeave(event) {
360
+ if (!interactive)
361
+ return;
362
+ dom.clearHoverIntent(event.currentTarget);
363
+ },
364
+ onClick() {
365
+ if (!interactive)
366
+ return;
367
+ send({ type: "CLICK_DELETE_TAG", id });
368
+ }
369
+ });
370
+ },
371
+ getClearTriggerProps() {
372
+ return normalize.button({
373
+ ...parts.clearTrigger.attrs,
374
+ "dir": state.context.dir,
375
+ "id": dom.getClearTriggerId(state.context),
376
+ "type": "button",
377
+ "data-readonly": dataAttr(readOnly),
378
+ "disabled": disabled,
379
+ "aria-label": translations.clearTriggerLabel,
380
+ "hidden": empty,
381
+ onClick() {
382
+ if (!interactive)
383
+ return;
384
+ send("CLEAR_VALUE");
385
+ }
386
+ });
387
+ }
388
+ };
389
+ }
390
+ function endsWith(str, del) {
391
+ if (!del)
392
+ return false;
393
+ if (typeof del === "string")
394
+ return str.endsWith(del);
395
+ return new RegExp(`${del.source}$`).test(str);
396
+ }
397
+
398
+ const { and, not, or } = guards;
399
+ const invoke = {
400
+ change: (ctx) => {
401
+ ctx.onValueChange?.({ value: Array.from(ctx.value) });
402
+ dom.dispatchInputEvent(ctx);
403
+ },
404
+ highlightChange: (ctx) => {
405
+ const highlightedValue = dom.getHighlightedTagValue(ctx);
406
+ ctx.onHighlightChange?.({ highlightedValue });
407
+ },
408
+ valueChange: (ctx) => {
409
+ ctx.onInputValueChange?.({ inputValue: ctx.inputValue });
410
+ }
411
+ };
412
+ const set = {
413
+ value: (ctx, value) => {
414
+ if (isEqual(ctx.value, value))
415
+ return;
416
+ ctx.value = value;
417
+ invoke.change(ctx);
418
+ },
419
+ valueAtIndex: (ctx, index, value) => {
420
+ if (isEqual(ctx.value[index], value))
421
+ return;
422
+ ctx.value[index] = value;
423
+ invoke.change(ctx);
424
+ },
425
+ highlightedId: (ctx, id) => {
426
+ if (isEqual(ctx.highlightedTagId, id))
427
+ return;
428
+ ctx.highlightedTagId = id;
429
+ invoke.highlightChange(ctx);
430
+ },
431
+ inputValue: (ctx, value) => {
432
+ if (isEqual(ctx.inputValue, value))
433
+ return;
434
+ ctx.inputValue = value;
435
+ invoke.valueChange(ctx);
436
+ }
437
+ };
438
+ function machine(userContext) {
439
+ const ctx = compact(userContext);
440
+ return createMachine(
441
+ {
442
+ id: "dynamic",
443
+ initial: ctx.autoFocus ? "focused:input" : "idle",
444
+ context: {
445
+ inputValue: "",
446
+ editedTagValue: "",
447
+ editedTagId: null,
448
+ highlightedTagId: null,
449
+ value: [],
450
+ dir: "ltr",
451
+ max: Infinity,
452
+ blurBehavior: void 0,
453
+ addOnPaste: false,
454
+ editable: true,
455
+ validate: () => true,
456
+ delimiter: ",",
457
+ disabled: false,
458
+ readOnly: false,
459
+ ...ctx,
460
+ liveRegion: null,
461
+ log: { current: null, prev: null },
462
+ fieldsetDisabled: false,
463
+ translations: {
464
+ clearTriggerLabel: "Clear all tags",
465
+ deleteTagTriggerLabel: (value) => `Delete tag ${value}`,
466
+ tagAdded: (value) => `Added tag ${value}`,
467
+ tagsPasted: (values) => `Pasted ${values.length} tags`,
468
+ tagEdited: (value) => `Editing tag ${value}. Press enter to save or escape to cancel.`,
469
+ tagUpdated: (value) => `Tag update to ${value}`,
470
+ tagDeleted: (value) => `Tag ${value} deleted`,
471
+ tagSelected: (value) => `Tag ${value} selected. Press enter to edit, delete or backspace to remove.`,
472
+ ...ctx.translations
473
+ }
474
+ },
475
+ computed: {
476
+ count: (ctx2) => ctx2.value.length,
477
+ valueAsString: (ctx2) => JSON.stringify(ctx2.value),
478
+ trimmedInputValue: (ctx2) => ctx2.inputValue.trim(),
479
+ isDisabled: (ctx2) => !!ctx2.disabled || ctx2.fieldsetDisabled,
480
+ isInteractive: (ctx2) => !(ctx2.readOnly || ctx2.isDisabled),
481
+ isAtMax: (ctx2) => ctx2.count === ctx2.max,
482
+ isOverflowing: (ctx2) => ctx2.count > ctx2.max
483
+ },
484
+ watch: {
485
+ highlightedTagId: "logHighlightedTag",
486
+ isOverflowing: "invokeOnInvalid",
487
+ log: "announceLog",
488
+ inputValue: "syncInputValue",
489
+ editedTagValue: "syncEditedTagInputValue"
490
+ },
491
+ activities: ["trackLiveRegion", "trackFormControlState"],
492
+ exit: ["clearLog"],
493
+ on: {
494
+ DOUBLE_CLICK_TAG: {
495
+ internal: true,
496
+ guard: "isTagEditable",
497
+ target: "editing:tag",
498
+ actions: ["setEditedId", "initializeEditedTagValue"]
499
+ },
500
+ POINTER_DOWN_TAG: {
501
+ internal: true,
502
+ target: "navigating:tag",
503
+ actions: ["highlightTag", "focusInput"]
504
+ },
505
+ CLICK_DELETE_TAG: {
506
+ target: "focused:input",
507
+ actions: ["deleteTag"]
508
+ },
509
+ SET_INPUT_VALUE: {
510
+ actions: ["setInputValue"]
511
+ },
512
+ SET_VALUE: {
513
+ actions: ["setValue"]
514
+ },
515
+ CLEAR_TAG: {
516
+ actions: ["deleteTag"]
517
+ },
518
+ SET_VALUE_AT_INDEX: {
519
+ actions: ["setValueAtIndex"]
520
+ },
521
+ CLEAR_VALUE: {
522
+ actions: ["clearTags", "clearInputValue", "focusInput"]
523
+ },
524
+ ADD_TAG: {
525
+ actions: ["addTag"]
526
+ },
527
+ INSERT_TAG: {
528
+ // (!isAtMax || allowOverflow) && !inputValueIsEmpty
529
+ guard: and(or(not("isAtMax"), "allowOverflow"), not("isInputValueEmpty")),
530
+ actions: ["addTag", "clearInputValue"]
531
+ },
532
+ EXTERNAL_BLUR: [
533
+ { guard: "addOnBlur", actions: "raiseInsertTagEvent" },
534
+ { guard: "clearOnBlur", actions: "clearInputValue" }
535
+ ]
536
+ },
537
+ states: {
538
+ "idle": {
539
+ on: {
540
+ FOCUS: "focused:input",
541
+ POINTER_DOWN: {
542
+ guard: not("hasHighlightedTag"),
543
+ target: "focused:input"
544
+ }
545
+ }
546
+ },
547
+ "focused:input": {
548
+ tags: ["focused"],
549
+ entry: ["focusInput", "clearHighlightedId"],
550
+ activities: ["trackInteractOutside"],
551
+ on: {
552
+ TYPE: {
553
+ actions: "setInputValue"
554
+ },
555
+ BLUR: [
556
+ {
557
+ guard: "addOnBlur",
558
+ target: "idle",
559
+ actions: "raiseInsertTagEvent"
560
+ },
561
+ {
562
+ guard: "clearOnBlur",
563
+ target: "idle",
564
+ actions: "clearInputValue"
565
+ },
566
+ { target: "idle" }
567
+ ],
568
+ ENTER: {
569
+ actions: ["raiseInsertTagEvent"]
570
+ },
571
+ DELIMITER_KEY: {
572
+ actions: ["raiseInsertTagEvent"]
573
+ },
574
+ ARROW_LEFT: {
575
+ guard: and("hasTags", "isInputCaretAtStart"),
576
+ target: "navigating:tag",
577
+ actions: "highlightLastTag"
578
+ },
579
+ BACKSPACE: {
580
+ target: "navigating:tag",
581
+ guard: and("hasTags", "isInputCaretAtStart"),
582
+ actions: "highlightLastTag"
583
+ },
584
+ DELETE: {
585
+ guard: "hasHighlightedTag",
586
+ actions: ["deleteHighlightedTag", "highlightTagAtIndex"]
587
+ },
588
+ PASTE: [
589
+ {
590
+ guard: "addOnPaste",
591
+ actions: ["setInputValue", "addTagFromPaste"]
592
+ },
593
+ {
594
+ actions: "setInputValue"
595
+ }
596
+ ]
597
+ }
598
+ },
599
+ "navigating:tag": {
600
+ tags: ["focused"],
601
+ activities: ["trackInteractOutside"],
602
+ on: {
603
+ ARROW_RIGHT: [
604
+ {
605
+ guard: and("hasTags", "isInputCaretAtStart", not("isLastTagHighlighted")),
606
+ actions: "highlightNextTag"
607
+ },
608
+ { target: "focused:input" }
609
+ ],
610
+ ARROW_LEFT: {
611
+ actions: "highlightPrevTag"
612
+ },
613
+ BLUR: {
614
+ target: "idle",
615
+ actions: "clearHighlightedId"
616
+ },
617
+ ENTER: {
618
+ guard: and("isTagEditable", "hasHighlightedTag"),
619
+ target: "editing:tag",
620
+ actions: ["setEditedId", "initializeEditedTagValue", "focusEditedTagInput"]
621
+ },
622
+ ARROW_DOWN: "focused:input",
623
+ ESCAPE: "focused:input",
624
+ TYPE: {
625
+ target: "focused:input",
626
+ actions: "setInputValue"
627
+ },
628
+ BACKSPACE: [
629
+ {
630
+ guard: "isFirstTagHighlighted",
631
+ actions: ["deleteHighlightedTag", "highlightFirstTag"]
632
+ },
633
+ {
634
+ guard: "hasHighlightedTag",
635
+ actions: ["deleteHighlightedTag", "highlightPrevTag"]
636
+ },
637
+ {
638
+ actions: ["highlightLastTag"]
639
+ }
640
+ ],
641
+ DELETE: {
642
+ target: "focused:input",
643
+ actions: ["deleteHighlightedTag", "highlightTagAtIndex"]
644
+ },
645
+ PASTE: [
646
+ {
647
+ guard: "addOnPaste",
648
+ target: "focused:input",
649
+ actions: ["setInputValue", "addTagFromPaste"]
650
+ },
651
+ {
652
+ target: "focused:input",
653
+ actions: "setInputValue"
654
+ }
655
+ ]
656
+ }
657
+ },
658
+ "editing:tag": {
659
+ tags: ["editing", "focused"],
660
+ entry: "focusEditedTagInput",
661
+ activities: ["autoResize"],
662
+ on: {
663
+ TAG_INPUT_TYPE: {
664
+ actions: "setEditedTagValue"
665
+ },
666
+ TAG_INPUT_ESCAPE: {
667
+ target: "navigating:tag",
668
+ actions: ["clearEditedTagValue", "focusInput", "clearEditedId", "highlightTagAtIndex"]
669
+ },
670
+ TAG_INPUT_BLUR: [
671
+ {
672
+ guard: "isInputRelatedTarget",
673
+ target: "navigating:tag",
674
+ actions: ["clearEditedTagValue", "clearHighlightedId", "clearEditedId"]
675
+ },
676
+ {
677
+ target: "idle",
678
+ actions: ["clearEditedTagValue", "clearHighlightedId", "clearEditedId", "raiseExternalBlurEvent"]
679
+ }
680
+ ],
681
+ TAG_INPUT_ENTER: [
682
+ {
683
+ guard: "isEditedTagEmpty",
684
+ target: "navigating:tag",
685
+ actions: ["deleteHighlightedTag", "focusInput", "clearEditedId", "highlightTagAtIndex"]
686
+ },
687
+ {
688
+ target: "navigating:tag",
689
+ actions: ["submitEditedTagValue", "focusInput", "clearEditedId", "highlightTagAtIndex"]
690
+ }
691
+ ]
692
+ }
693
+ }
694
+ }
695
+ },
696
+ {
697
+ guards: {
698
+ isInputRelatedTarget: (ctx2, evt) => evt.relatedTarget === dom.getInputEl(ctx2),
699
+ isAtMax: (ctx2) => ctx2.isAtMax,
700
+ hasHighlightedTag: (ctx2) => ctx2.highlightedTagId !== null,
701
+ isFirstTagHighlighted: (ctx2) => {
702
+ const firstItemId = dom.getItemId(ctx2, { value: ctx2.value[0], index: 0 });
703
+ return firstItemId === ctx2.highlightedTagId;
704
+ },
705
+ isEditedTagEmpty: (ctx2) => ctx2.editedTagValue.trim() === "",
706
+ isLastTagHighlighted: (ctx2) => {
707
+ const lastIndex = ctx2.value.length - 1;
708
+ const lastItemId = dom.getItemId(ctx2, { value: ctx2.value[lastIndex], index: lastIndex });
709
+ return lastItemId === ctx2.highlightedTagId;
710
+ },
711
+ isInputValueEmpty: (ctx2) => ctx2.trimmedInputValue.length === 0,
712
+ hasTags: (ctx2) => ctx2.value.length > 0,
713
+ allowOverflow: (ctx2) => !!ctx2.allowOverflow,
714
+ autoFocus: (ctx2) => !!ctx2.autoFocus,
715
+ addOnBlur: (ctx2) => ctx2.blurBehavior === "add",
716
+ clearOnBlur: (ctx2) => ctx2.blurBehavior === "clear",
717
+ addOnPaste: (ctx2) => !!ctx2.addOnPaste,
718
+ isTagEditable: (ctx2) => !!ctx2.editable,
719
+ isInputCaretAtStart(ctx2) {
720
+ const input = dom.getInputEl(ctx2);
721
+ if (!input)
722
+ return false;
723
+ try {
724
+ return input.selectionStart === 0 && input.selectionEnd === 0;
725
+ } catch {
726
+ return input.value === "";
727
+ }
728
+ }
729
+ },
730
+ activities: {
731
+ trackInteractOutside(ctx2, _evt, { send }) {
732
+ return trackInteractOutside(dom.getInputEl(ctx2), {
733
+ exclude(target) {
734
+ return contains(dom.getRootEl(ctx2), target);
735
+ },
736
+ onFocusOutside: ctx2.onFocusOutside,
737
+ onPointerDownOutside: ctx2.onPointerDownOutside,
738
+ onInteractOutside(event) {
739
+ ctx2.onInteractOutside?.(event);
740
+ if (event.defaultPrevented)
741
+ return;
742
+ send({ type: "BLUR", src: "interact-outside" });
743
+ }
744
+ });
745
+ },
746
+ trackFormControlState(ctx2, _evt, { send, initialContext }) {
747
+ return trackFormControl(dom.getHiddenInputEl(ctx2), {
748
+ onFieldsetDisabledChange(disabled) {
749
+ ctx2.fieldsetDisabled = disabled;
750
+ },
751
+ onFormReset() {
752
+ send({ type: "SET_VALUE", value: initialContext.value, src: "form-reset" });
753
+ }
754
+ });
755
+ },
756
+ autoResize(ctx2) {
757
+ if (!ctx2.editedTagValue || ctx2.idx == null || !ctx2.editable)
758
+ return;
759
+ const input = dom.getTagInputEl(ctx2, { value: ctx2.editedTagValue, index: ctx2.idx });
760
+ return autoResizeInput(input);
761
+ },
762
+ trackLiveRegion(ctx2) {
763
+ ctx2.liveRegion = createLiveRegion({
764
+ level: "assertive",
765
+ document: dom.getDoc(ctx2)
766
+ });
767
+ return () => ctx2.liveRegion?.destroy();
768
+ }
769
+ },
770
+ actions: {
771
+ raiseInsertTagEvent(_, __, { self }) {
772
+ self.send("INSERT_TAG");
773
+ },
774
+ raiseExternalBlurEvent(_, evt, { self }) {
775
+ self.send({ type: "EXTERNAL_BLUR", id: evt.id });
776
+ },
777
+ dispatchChangeEvent(ctx2) {
778
+ dom.dispatchInputEvent(ctx2);
779
+ },
780
+ highlightNextTag(ctx2) {
781
+ if (ctx2.highlightedTagId == null)
782
+ return;
783
+ const next = dom.getNextEl(ctx2, ctx2.highlightedTagId);
784
+ set.highlightedId(ctx2, next?.id ?? null);
785
+ },
786
+ highlightFirstTag(ctx2) {
787
+ raf(() => {
788
+ const first = dom.getFirstEl(ctx2);
789
+ set.highlightedId(ctx2, first?.id ?? null);
790
+ });
791
+ },
792
+ highlightLastTag(ctx2) {
793
+ const last = dom.getLastEl(ctx2);
794
+ set.highlightedId(ctx2, last?.id ?? null);
795
+ },
796
+ highlightPrevTag(ctx2) {
797
+ if (ctx2.highlightedTagId == null)
798
+ return;
799
+ const prev = dom.getPrevEl(ctx2, ctx2.highlightedTagId);
800
+ set.highlightedId(ctx2, prev?.id ?? null);
801
+ },
802
+ highlightTag(ctx2, evt) {
803
+ set.highlightedId(ctx2, evt.id);
804
+ },
805
+ highlightTagAtIndex(ctx2) {
806
+ raf(() => {
807
+ if (ctx2.idx == null)
808
+ return;
809
+ const tagEl = dom.getTagElAtIndex(ctx2, ctx2.idx);
810
+ if (tagEl == null)
811
+ return;
812
+ set.highlightedId(ctx2, tagEl.id);
813
+ ctx2.idx = void 0;
814
+ });
815
+ },
816
+ deleteTag(ctx2, evt) {
817
+ const index = dom.getIndexOfId(ctx2, evt.id);
818
+ const value = ctx2.value[index];
819
+ ctx2.log.prev = ctx2.log.current;
820
+ ctx2.log.current = { type: "delete", value };
821
+ set.value(ctx2, removeAt(ctx2.value, index));
822
+ },
823
+ deleteHighlightedTag(ctx2) {
824
+ if (ctx2.highlightedTagId == null)
825
+ return;
826
+ const index = dom.getIndexOfId(ctx2, ctx2.highlightedTagId);
827
+ ctx2.idx = index;
828
+ const value = ctx2.value[index];
829
+ ctx2.log.prev = ctx2.log.current;
830
+ ctx2.log.current = { type: "delete", value };
831
+ set.value(ctx2, removeAt(ctx2.value, index));
832
+ },
833
+ setEditedId(ctx2, evt) {
834
+ ctx2.editedTagId = evt.id ?? ctx2.highlightedTagId;
835
+ ctx2.idx = dom.getIndexOfId(ctx2, ctx2.editedTagId);
836
+ },
837
+ clearEditedId(ctx2) {
838
+ ctx2.editedTagId = null;
839
+ },
840
+ clearEditedTagValue(ctx2) {
841
+ ctx2.editedTagValue = "";
842
+ },
843
+ setEditedTagValue(ctx2, evt) {
844
+ ctx2.editedTagValue = evt.value;
845
+ },
846
+ submitEditedTagValue(ctx2) {
847
+ if (!ctx2.editedTagId)
848
+ return;
849
+ const index = dom.getIndexOfId(ctx2, ctx2.editedTagId);
850
+ set.valueAtIndex(ctx2, index, ctx2.editedTagValue ?? "");
851
+ ctx2.log.prev = ctx2.log.current;
852
+ ctx2.log.current = { type: "update", value: ctx2.editedTagValue };
853
+ },
854
+ setValueAtIndex(ctx2, evt) {
855
+ if (evt.value) {
856
+ ctx2.value[evt.index] = evt.value;
857
+ ctx2.log.prev = ctx2.log.current;
858
+ ctx2.log.current = { type: "update", value: evt.value };
859
+ } else {
860
+ warn("You need to provide a value for the tag");
861
+ }
862
+ },
863
+ initializeEditedTagValue(ctx2) {
864
+ if (!ctx2.editedTagId)
865
+ return;
866
+ const index = dom.getIndexOfId(ctx2, ctx2.editedTagId);
867
+ ctx2.editedTagValue = ctx2.value[index];
868
+ },
869
+ focusEditedTagInput(ctx2) {
870
+ raf(() => {
871
+ dom.getEditInputEl(ctx2)?.select();
872
+ });
873
+ },
874
+ setInputValue(ctx2, evt) {
875
+ set.inputValue(ctx2, evt.value);
876
+ },
877
+ clearHighlightedId(ctx2) {
878
+ ctx2.highlightedTagId = null;
879
+ },
880
+ focusInput(ctx2) {
881
+ raf(() => {
882
+ dom.getInputEl(ctx2)?.focus();
883
+ });
884
+ },
885
+ clearInputValue(ctx2) {
886
+ raf(() => {
887
+ set.inputValue(ctx2, "");
888
+ });
889
+ },
890
+ syncInputValue(ctx2) {
891
+ const inputEl = dom.getInputEl(ctx2);
892
+ dom.setValue(inputEl, ctx2.inputValue);
893
+ },
894
+ syncEditedTagInputValue(ctx2, evt) {
895
+ const id = ctx2.editedTagId || ctx2.highlightedTagId || evt.id;
896
+ if (id == null)
897
+ return;
898
+ const editTagInputEl = dom.getById(ctx2, `${id}:input`);
899
+ dom.setValue(editTagInputEl, ctx2.editedTagValue);
900
+ },
901
+ addTag(ctx2, evt) {
902
+ const value = evt.value ?? ctx2.trimmedInputValue;
903
+ const guard = ctx2.validate?.({ inputValue: value, value: Array.from(ctx2.value) });
904
+ if (guard) {
905
+ const nextValue = uniq(ctx2.value.concat(value));
906
+ set.value(ctx2, nextValue);
907
+ ctx2.log.prev = ctx2.log.current;
908
+ ctx2.log.current = { type: "add", value };
909
+ } else {
910
+ ctx2.onValueInvalid?.({ reason: "invalidTag" });
911
+ }
912
+ },
913
+ addTagFromPaste(ctx2) {
914
+ raf(() => {
915
+ const value = ctx2.trimmedInputValue;
916
+ const guard = ctx2.validate?.({ inputValue: value, value: Array.from(ctx2.value) });
917
+ if (guard) {
918
+ const trimmedValue = ctx2.delimiter ? value.split(ctx2.delimiter).map((v) => v.trim()) : [value];
919
+ const nextValue = uniq(ctx2.value.concat(...trimmedValue));
920
+ set.value(ctx2, nextValue);
921
+ ctx2.log.prev = ctx2.log.current;
922
+ ctx2.log.current = { type: "paste", values: trimmedValue };
923
+ } else {
924
+ ctx2.onValueInvalid?.({ reason: "invalidTag" });
925
+ }
926
+ set.inputValue(ctx2, "");
927
+ });
928
+ },
929
+ clearTags(ctx2) {
930
+ set.value(ctx2, []);
931
+ ctx2.log.prev = ctx2.log.current;
932
+ ctx2.log.current = { type: "clear" };
933
+ },
934
+ setValue(ctx2, evt) {
935
+ set.value(ctx2, evt.value);
936
+ },
937
+ invokeOnInvalid(ctx2) {
938
+ if (ctx2.isOverflowing) {
939
+ ctx2.onValueInvalid?.({ reason: "rangeOverflow" });
940
+ }
941
+ },
942
+ clearLog(ctx2) {
943
+ ctx2.log = { prev: null, current: null };
944
+ },
945
+ logHighlightedTag(ctx2) {
946
+ if (ctx2.highlightedTagId == null)
947
+ return;
948
+ const index = dom.getIndexOfId(ctx2, ctx2.highlightedTagId);
949
+ ctx2.log.prev = ctx2.log.current;
950
+ ctx2.log.current = { type: "select", value: ctx2.value[index] };
951
+ },
952
+ // queue logs with screen reader and get it announced
953
+ announceLog(ctx2) {
954
+ if (!ctx2.log.current || ctx2.liveRegion == null)
955
+ return;
956
+ const region = ctx2.liveRegion;
957
+ const { current, prev } = ctx2.log;
958
+ let msg;
959
+ switch (current.type) {
960
+ case "add":
961
+ msg = ctx2.translations.tagAdded(current.value);
962
+ break;
963
+ case "delete":
964
+ msg = ctx2.translations.tagDeleted(current.value);
965
+ break;
966
+ case "update":
967
+ msg = ctx2.translations.tagUpdated(current.value);
968
+ break;
969
+ case "paste":
970
+ msg = ctx2.translations.tagsPasted(current.values);
971
+ break;
972
+ case "select":
973
+ msg = ctx2.translations.tagSelected(current.value);
974
+ if (prev?.type === "delete") {
975
+ msg = `${ctx2.translations.tagDeleted(prev.value)}. ${msg}`;
976
+ } else if (prev?.type === "update") {
977
+ msg = `${ctx2.translations.tagUpdated(prev.value)}. ${msg}`;
978
+ }
979
+ break;
980
+ }
981
+ if (msg)
982
+ region.announce(msg);
983
+ }
984
+ }
985
+ }
986
+ );
987
+ }
988
+
989
+ const props = createProps()([
990
+ "addOnPaste",
991
+ "allowOverflow",
992
+ "autoFocus",
993
+ "blurBehavior",
994
+ "delimiter",
995
+ "dir",
996
+ "disabled",
997
+ "editable",
998
+ "form",
999
+ "getRootNode",
1000
+ "id",
1001
+ "ids",
1002
+ "inputValue",
1003
+ "invalid",
1004
+ "max",
1005
+ "maxLength",
1006
+ "name",
1007
+ "onFocusOutside",
1008
+ "onHighlightChange",
1009
+ "onInputValueChange",
1010
+ "onInteractOutside",
1011
+ "onPointerDownOutside",
1012
+ "onValueChange",
1013
+ "onValueInvalid",
1014
+ "required",
1015
+ "readOnly",
1016
+ "translations",
1017
+ "validate",
1018
+ "value"
1019
+ ]);
1020
+ const splitProps = createSplitProps(props);
1021
+ const itemProps = createProps()(["index", "disabled", "value"]);
1022
+ const splitItemProps = createSplitProps(itemProps);
1023
+
1024
+ export { anatomy, connect, itemProps, machine, props, splitItemProps, splitProps };