@destyler/combobox 0.2.6 → 0.2.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.d.mts CHANGED
@@ -7,7 +7,7 @@ import { FocusOutsideEvent, InteractOutsideEvent, InteractOutsideHandlers, Point
7
7
  import { CommonProperties, DirectionProperty, NormalizeProps, PropTypes, RequiredBy } from "@destyler/types";
8
8
 
9
9
  //#region src/anatomy.d.ts
10
- declare const anatomy: _destyler_anatomy0.AnatomyInstance<"root" | "clearTrigger" | "content" | "control" | "input" | "item" | "itemGroup" | "itemGroupLabel" | "itemIndicator" | "itemText" | "label" | "list" | "positioner" | "trigger">;
10
+ declare const anatomy: _destyler_anatomy0.AnatomyInstance<"root" | "label" | "control" | "input" | "content" | "trigger" | "clearTrigger" | "item" | "positioner" | "itemGroup" | "itemGroupLabel" | "itemIndicator" | "itemText" | "list">;
11
11
  //#endregion
12
12
  //#region src/collection.d.ts
13
13
  declare function collection<T extends CollectionItem$1>(options: CollectionOptions$1<T>): ListCollection<T>;
@@ -59,56 +59,67 @@ interface PublicContext<T extends CollectionItem = CollectionItem> extends Direc
59
59
  /**
60
60
  * Whether the combobox is open
61
61
  */
62
- 'open'?: boolean | undefined;
62
+ open?: boolean | undefined;
63
63
  /**
64
- * Whether the combobox open state is controlled by the user
64
+ * The initial open state of the combobox when it is not controlled.
65
+ * Prefer this over seeding with `open` for uncontrolled usage.
65
66
  */
66
- 'open.controlled'?: boolean | undefined;
67
+ defaultOpen?: boolean | undefined;
67
68
  /**
68
69
  * The ids of the elements in the combobox. Useful for composition.
69
70
  */
70
- 'ids'?: ElementIds | undefined;
71
+ ids?: ElementIds | undefined;
71
72
  /**
72
- * The current value of the combobox's input
73
+ * The controlled value of the combobox's input
73
74
  */
74
- 'inputValue': string;
75
+ inputValue?: string | undefined;
76
+ /**
77
+ * The initial value of the combobox's input when it is not controlled.
78
+ * Prefer this over seeding with `inputValue` for uncontrolled usage.
79
+ */
80
+ defaultInputValue?: string | undefined;
75
81
  /**
76
82
  * The `name` attribute of the combobox's input. Useful for form submission
77
83
  */
78
- 'name'?: string | undefined;
84
+ name?: string | undefined;
79
85
  /**
80
86
  * The associate form of the combobox.
81
87
  */
82
- 'form'?: string | undefined;
88
+ form?: string | undefined;
83
89
  /**
84
90
  * Whether the combobox is disabled
85
91
  */
86
- 'disabled'?: boolean | undefined;
92
+ disabled?: boolean | undefined;
87
93
  /**
88
94
  * Whether the combobox is readonly. This puts the combobox in a "non-editable" mode
89
95
  * but the user can still interact with it
90
96
  */
91
- 'readOnly'?: boolean | undefined;
97
+ readOnly?: boolean | undefined;
92
98
  /**
93
99
  * Whether the combobox is invalid
94
100
  */
95
- 'invalid'?: boolean | undefined;
101
+ invalid?: boolean | undefined;
96
102
  /**
97
103
  * Whether the combobox is required
98
104
  */
99
- 'required'?: boolean | undefined;
105
+ required?: boolean | undefined;
100
106
  /**
101
107
  * The placeholder text of the combobox's input
102
108
  */
103
- 'placeholder'?: string | undefined;
109
+ placeholder?: string | undefined;
104
110
  /**
105
111
  * The active item's id. Used to set the `aria-activedescendant` attribute
106
112
  */
107
- 'highlightedValue': string | null;
113
+ highlightedValue: string | null;
114
+ /**
115
+ * The controlled keys of the selected items
116
+ */
117
+ value?: string[] | undefined;
108
118
  /**
109
- * The keys of the selected items
119
+ * The initial keys of the selected items when it is not controlled.
120
+ * Prefer this over seeding with `value` for uncontrolled usage.
110
121
  */
111
- 'value': string[];
122
+ defaultValue?: string[] | undefined;
112
123
  /**
113
124
  * Defines the auto-completion behavior of the combobox.
114
125
  *
@@ -117,7 +128,7 @@ interface PublicContext<T extends CollectionItem = CollectionItem> extends Direc
117
128
  *
118
129
  * @default "none"
119
130
  */
120
- 'inputBehavior': 'autohighlight' | 'autocomplete' | 'none';
131
+ inputBehavior: 'autohighlight' | 'autocomplete' | 'none';
121
132
  /**
122
133
  * The behavior of the combobox input when an item is selected
123
134
  *
@@ -127,92 +138,92 @@ interface PublicContext<T extends CollectionItem = CollectionItem> extends Direc
127
138
  *
128
139
  * @default "replace"
129
140
  */
130
- 'selectionBehavior': 'clear' | 'replace' | 'preserve';
141
+ selectionBehavior: 'clear' | 'replace' | 'preserve';
131
142
  /**
132
143
  * Whether to autofocus the input on mount
133
144
  */
134
- 'autoFocus'?: boolean | undefined;
145
+ autoFocus?: boolean | undefined;
135
146
  /**
136
147
  * Whether to open the combobox popup on initial click on the input
137
148
  * @default false
138
149
  */
139
- 'openOnClick'?: boolean | undefined;
150
+ openOnClick?: boolean | undefined;
140
151
  /**
141
152
  * Whether to show the combobox when the input value changes
142
153
  * @default true
143
154
  */
144
- 'openOnChange'?: boolean | ((details: InputValueChangeDetails) => boolean) | undefined;
155
+ openOnChange?: boolean | ((details: InputValueChangeDetails) => boolean) | undefined;
145
156
  /**
146
157
  * Whether to allow typing custom values in the input
147
158
  */
148
- 'allowCustomValue'?: boolean | undefined;
159
+ allowCustomValue?: boolean | undefined;
149
160
  /**
150
161
  * Whether to loop the keyboard navigation through the items
151
162
  * @default true
152
163
  */
153
- 'loopFocus'?: boolean | undefined;
164
+ loopFocus?: boolean | undefined;
154
165
  /**
155
166
  * The positioning options to dynamically position the menu
156
167
  */
157
- 'positioning': PositioningOptions;
168
+ positioning: PositioningOptions;
158
169
  /**
159
170
  * Function called when the input's value changes
160
171
  */
161
- 'onInputValueChange'?: ((details: InputValueChangeDetails) => void) | undefined;
172
+ onInputValueChange?: ((details: InputValueChangeDetails) => void) | undefined;
162
173
  /**
163
174
  * Function called when a new item is selected
164
175
  */
165
- 'onValueChange'?: ((details: ValueChangeDetails<T>) => void) | undefined;
176
+ onValueChange?: ((details: ValueChangeDetails<T>) => void) | undefined;
166
177
  /**
167
178
  * Function called when an item is highlighted using the pointer
168
179
  * or keyboard navigation.
169
180
  */
170
- 'onHighlightChange'?: ((details: HighlightChangeDetails<T>) => void) | undefined;
181
+ onHighlightChange?: ((details: HighlightChangeDetails<T>) => void) | undefined;
171
182
  /**
172
183
  * Function called when the popup is opened
173
184
  */
174
- 'onOpenChange'?: ((details: OpenChangeDetails) => void) | undefined;
185
+ onOpenChange?: ((details: OpenChangeDetails) => void) | undefined;
175
186
  /**
176
187
  * Specifies the localized strings that identifies the accessibility elements and their states
177
188
  */
178
- 'translations': IntlTranslations;
189
+ translations: IntlTranslations;
179
190
  /**
180
191
  * The collection of items
181
192
  */
182
- 'collection': ListCollection<any>;
193
+ collection: ListCollection<any>;
183
194
  /**
184
195
  * Whether to allow multiple selection.
185
196
  *
186
197
  * **Good to know:** When `multiple` is `true`, the `selectionBehavior` is automatically set to `clear`.
187
198
  * It is recommended to render the selected items in a separate container.
188
199
  */
189
- 'multiple'?: boolean | undefined;
200
+ multiple?: boolean | undefined;
190
201
  /**
191
202
  * Whether to close the combobox when an item is selected.
192
203
  */
193
- 'closeOnSelect'?: boolean | undefined;
204
+ closeOnSelect?: boolean | undefined;
194
205
  /**
195
206
  * Whether to open the combobox on arrow key press
196
207
  * @default true
197
208
  */
198
- 'openOnKeyPress': boolean;
209
+ openOnKeyPress: boolean;
199
210
  /**
200
211
  * Function to scroll to a specific index
201
212
  */
202
- 'scrollToIndexFn'?: ((details: ScrollToIndexDetails) => void) | undefined;
213
+ scrollToIndexFn?: ((details: ScrollToIndexDetails) => void) | undefined;
203
214
  /**
204
215
  * Whether the combobox is a composed with other composite widgets like tabs
205
216
  * @default true
206
217
  */
207
- 'composite': boolean;
218
+ composite: boolean;
208
219
  /**
209
220
  * Whether to disable registering this a dismissable layer
210
221
  */
211
- 'disableLayer'?: boolean | undefined;
222
+ disableLayer?: boolean | undefined;
212
223
  /**
213
224
  * Function to navigate to the selected item
214
225
  */
215
- 'navigate': (details: NavigateDetails) => void;
226
+ navigate: (details: NavigateDetails) => void;
216
227
  }
217
228
  type UserDefinedContext<T extends CollectionItem = CollectionItem> = RequiredBy<PublicContext<T>, 'id' | 'collection'>;
218
229
  type ComputedContext = Readonly<{
package/dist/index.mjs CHANGED
@@ -5,7 +5,7 @@ import { ariaAttr, clickIfLink, createScope, dataAttr, getEventKey, isAnchorElem
5
5
  import { getPlacement, getPlacementStyles } from "@destyler/popper";
6
6
  import { ariaHidden } from "@destyler/aria-hidden";
7
7
  import { trackDismissableElement } from "@destyler/dismissable";
8
- import { addOrRemove, compact, isArray, isBoolean, isEqual, match } from "@destyler/utils";
8
+ import { addOrRemove, compact, isArray, isBoolean, isControlled, isEqual, isPropUserProvided, match, resolveControllableOpen, resolveControllableProp, withControllableProvided } from "@destyler/utils";
9
9
 
10
10
  //#region src/anatomy.ts
11
11
  const anatomy = createAnatomy("combobox").parts("root", "clearTrigger", "content", "control", "input", "item", "itemGroup", "itemGroupLabel", "itemIndicator", "itemText", "label", "list", "positioner", "trigger");
@@ -389,7 +389,7 @@ function connect(state, send, normalize) {
389
389
  return normalize.element({
390
390
  ...parts.list.attrs,
391
391
  "role": !composite ? "listbox" : void 0,
392
- "aria-labelledby": dom.getLabelId(state.context),
392
+ "aria-labelledby": !composite ? dom.getLabelId(state.context) : void 0,
393
393
  "aria-multiselectable": state.context.multiple && !composite ? true : void 0
394
394
  });
395
395
  },
@@ -488,6 +488,7 @@ function connect(state, send, normalize) {
488
488
  const { id } = props;
489
489
  return normalize.element({
490
490
  ...parts.itemGroup.attrs,
491
+ "role": "group",
491
492
  "dir": state.context.dir,
492
493
  "id": dom.getItemGroupId(state.context, id),
493
494
  "aria-labelledby": dom.getItemGroupLabelId(state.context, id)
@@ -498,8 +499,7 @@ function connect(state, send, normalize) {
498
499
  return normalize.element({
499
500
  ...parts.itemGroupLabel.attrs,
500
501
  dir: state.context.dir,
501
- id: dom.getItemGroupLabelId(state.context, htmlFor),
502
- role: "group"
502
+ id: dom.getItemGroupLabelId(state.context, htmlFor)
503
503
  });
504
504
  }
505
505
  };
@@ -548,17 +548,32 @@ const invoke = {
548
548
  ctx.onInputValueChange?.({ inputValue: ctx.inputValue });
549
549
  }
550
550
  };
551
+ function proposeValueChange(ctx, value) {
552
+ const prevSelectedItems = ctx.selectedItems;
553
+ const items = value.map((v) => {
554
+ const foundItem = prevSelectedItems.find((item) => ctx.collection.getItemValue(item) === v);
555
+ if (foundItem) return foundItem;
556
+ return ctx.collection.find(v);
557
+ });
558
+ ctx.onValueChange?.({
559
+ value: Array.from(value),
560
+ items: Array.from(items)
561
+ });
562
+ }
551
563
  const set = {
552
564
  value: (ctx, value, force = false) => {
553
565
  if (isEqual(ctx.value, value)) return;
554
566
  if (value == null && !force) return;
555
- if (value == null && force) {
556
- ctx.value = [];
557
- invoke.valueChange(ctx);
567
+ let next;
568
+ if (value == null) next = [];
569
+ else if (isArray(value)) next = value;
570
+ else next = ctx.multiple ? addOrRemove(ctx.value, value) : [value];
571
+ if (isEqual(ctx.value, next)) return;
572
+ if (isControlled(ctx, "value")) {
573
+ proposeValueChange(ctx, next);
558
574
  return;
559
575
  }
560
- if (isArray(value)) ctx.value = value;
561
- else if (value != null) ctx.value = ctx.multiple ? addOrRemove(ctx.value, value) : [value];
576
+ ctx.value = next;
562
577
  invoke.valueChange(ctx);
563
578
  },
564
579
  highlightedValue: (ctx, value, force = false) => {
@@ -569,15 +584,36 @@ const set = {
569
584
  },
570
585
  inputValue: (ctx, value) => {
571
586
  if (isEqual(ctx.inputValue, value)) return;
587
+ if (isControlled(ctx, "inputValue")) {
588
+ ctx.onInputValueChange?.({ inputValue: value });
589
+ return;
590
+ }
572
591
  ctx.inputValue = value;
573
592
  invoke.inputChange(ctx);
574
593
  }
575
594
  };
576
595
  function machine(userContext) {
577
- const ctx = compact(userContext);
596
+ const ctx = compact(withControllableProvided(userContext, [
597
+ "open",
598
+ "value",
599
+ "inputValue"
600
+ ]));
601
+ const { initialOpen } = resolveControllableOpen(ctx);
602
+ const { initial: initialValue } = resolveControllableProp({
603
+ value: ctx.value,
604
+ defaultValue: ctx.defaultValue,
605
+ valueProvided: isPropUserProvided(ctx, "value"),
606
+ fallback: []
607
+ });
608
+ const { initial: initialInputValue } = resolveControllableProp({
609
+ value: ctx.inputValue,
610
+ defaultValue: ctx.defaultInputValue,
611
+ valueProvided: isPropUserProvided(ctx, "inputValue"),
612
+ fallback: ""
613
+ });
578
614
  return createMachine({
579
615
  id: "combobox",
580
- initial: ctx.open ? "suggesting" : "idle",
616
+ initial: initialOpen ? "suggesting" : "idle",
581
617
  context: {
582
618
  loopFocus: true,
583
619
  openOnClick: false,
@@ -597,6 +633,8 @@ function machine(userContext) {
597
633
  clickIfLink(node);
598
634
  },
599
635
  ...ctx,
636
+ value: initialValue,
637
+ inputValue: initialInputValue,
600
638
  highlightedItem: null,
601
639
  selectedItems: [],
602
640
  valueAsString: "",
@@ -1091,7 +1129,7 @@ function machine(userContext) {
1091
1129
  allowCustomValue: (ctx$1) => !!ctx$1.allowCustomValue,
1092
1130
  hasHighlightedItem: (ctx$1) => ctx$1.highlightedValue != null,
1093
1131
  closeOnSelect: (ctx$1) => !!ctx$1.closeOnSelect,
1094
- isOpenControlled: (ctx$1) => !!ctx$1["open.controlled"],
1132
+ isOpenControlled: (ctx$1) => isControlled(ctx$1, "open"),
1095
1133
  openOnChange: (ctx$1, evt) => {
1096
1134
  if (isBoolean(ctx$1.openOnChange)) return ctx$1.openOnChange;
1097
1135
  return !!ctx$1.openOnChange?.({ inputValue: evt.value });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@destyler/combobox",
3
3
  "type": "module",
4
- "version": "0.2.6",
4
+ "version": "0.2.9",
5
5
  "description": "autocomplete input and command palette with a list of suggestions.",
6
6
  "author": "Elone Hoo <elonehoo@gmail.com>",
7
7
  "license": "MIT",
@@ -22,15 +22,15 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
- "@destyler/anatomy": "0.2.6",
26
- "@destyler/aria-hidden": "0.2.6",
27
- "@destyler/collection": "0.2.6",
28
- "@destyler/dismissable": "0.2.6",
29
- "@destyler/dom": "0.2.6",
30
- "@destyler/popper": "0.2.6",
31
- "@destyler/xstate": "0.2.6",
32
- "@destyler/types": "0.2.6",
33
- "@destyler/utils": "0.2.6"
25
+ "@destyler/aria-hidden": "0.2.9",
26
+ "@destyler/anatomy": "0.2.9",
27
+ "@destyler/collection": "0.2.9",
28
+ "@destyler/popper": "0.2.9",
29
+ "@destyler/types": "0.2.9",
30
+ "@destyler/dismissable": "0.2.9",
31
+ "@destyler/utils": "0.2.9",
32
+ "@destyler/dom": "0.2.9",
33
+ "@destyler/xstate": "0.2.9"
34
34
  },
35
35
  "publishConfig": {
36
36
  "access": "public"