@jirawatpyk/aura-react 4.8.0 → 4.9.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.
@@ -18,257 +18,332 @@ function defaultFilter(option, query) {
18
18
  return norm(k).indexOf(q) >= 0;
19
19
  });
20
20
  }
21
- const Combobox = React.forwardRef(function Combobox2(props, ref) {
22
- const t = useStrings();
23
- const auto = uid(), id = props.id || auto, listId = id + "-list";
24
- const options = (props.options || []).map(toOpt);
25
- const st = useMaybeControlled(
26
- props.value,
27
- props.defaultValue == null ? null : props.defaultValue,
28
- props.onChange
29
- );
30
- const value = st[0], setValue = st[1];
31
- const selected = options.filter(function(o) {
32
- return o.value === value;
33
- })[0] || null;
34
- const openState = React.useState(false), open = openState[0], setOpen = openState[1];
35
- const qState = React.useState(null), query = qState[0], setQuery = qState[1];
36
- const aState = React.useState(0), active = aState[0], setActive = aState[1];
37
- const posState = React.useState(null);
38
- const inputRef = React.useRef(null), inputMerged = useMergedRef(ref, inputRef), boxRef = React.useRef(null), listRef = React.useRef(null);
39
- const mounted = useMounted();
40
- const limit = props.limit || 200;
41
- const filter = props.filter || defaultFilter;
42
- let shown = props.onSearch || query == null ? options : options.filter(function(o) {
43
- return filter(o, query);
44
- });
45
- const more = shown.length > limit;
46
- shown = shown.slice(0, limit);
47
- const activeIdx = Math.min(active, shown.length - 1);
48
- function place() {
49
- if (!boxRef.current) return;
50
- const r = boxRef.current.getBoundingClientRect();
51
- const maxH = 320, below = window.innerHeight - r.bottom - 8, up = below < 200 && r.top > below;
52
- posState[1]({
53
- left: r.left,
54
- width: r.width,
55
- top: up ? void 0 : r.bottom + 4,
56
- bottom: up ? window.innerHeight - r.top + 4 : void 0,
57
- maxHeight: Math.min(maxH, (up ? r.top : below) - 8)
21
+ const Combobox = React.forwardRef(
22
+ function Combobox2(all, ref) {
23
+ const props = all;
24
+ const multi = all.multiple === true;
25
+ const mp = all;
26
+ const t = useStrings();
27
+ const auto = uid(), id = props.id || auto, listId = id + "-list";
28
+ const options = (props.options || []).map(toOpt);
29
+ const st = useMaybeControlled(
30
+ multi ? mp.value : props.value === void 0 ? void 0 : props.value == null ? [] : [props.value],
31
+ multi ? mp.defaultValue || [] : props.defaultValue == null ? [] : [props.defaultValue],
32
+ function(next) {
33
+ if (multi) {
34
+ if (mp.onChange) mp.onChange(next);
35
+ } else if (props.onChange) props.onChange(next.length ? next[0] : null);
36
+ }
37
+ );
38
+ const values = st[0], setValues = st[1];
39
+ const value = values.length ? values[0] : null;
40
+ function setValue(v) {
41
+ setValues(v == null ? [] : [v]);
42
+ }
43
+ const isPicked = function(v) {
44
+ return values.indexOf(v) >= 0;
45
+ };
46
+ const full = multi && mp.max != null && values.length >= mp.max;
47
+ const selected = multi ? null : options.filter(function(o) {
48
+ return o.value === value;
49
+ })[0] || null;
50
+ const picked = multi ? values.map(function(v) {
51
+ return options.filter(function(o) {
52
+ return o.value === v;
53
+ })[0];
54
+ }).filter(Boolean) : [];
55
+ const openState = React.useState(false), open = openState[0], setOpen = openState[1];
56
+ const qState = React.useState(null), query = qState[0], setQuery = qState[1];
57
+ const aState = React.useState(0), active = aState[0], setActive = aState[1];
58
+ const posState = React.useState(null);
59
+ const inputRef = React.useRef(null), inputMerged = useMergedRef(ref, inputRef), boxRef = React.useRef(null), listRef = React.useRef(null);
60
+ const mounted = useMounted();
61
+ const limit = props.limit || 200;
62
+ const filter = props.filter || defaultFilter;
63
+ let shown = props.onSearch || query == null ? options : options.filter(function(o) {
64
+ return filter(o, query);
58
65
  });
59
- }
60
- useIsoLayoutEffect(
61
- function() {
62
- if (open) place();
63
- },
64
- [open, shown.length]
65
- );
66
- React.useEffect(
67
- function() {
68
- if (!open) return;
69
- function outside(e) {
70
- if (boxRef.current && boxRef.current.contains(e.target)) return;
71
- if (listRef.current && listRef.current.contains(e.target)) return;
72
- close(false);
66
+ const more = shown.length > limit;
67
+ shown = shown.slice(0, limit);
68
+ const activeIdx = Math.min(active, shown.length - 1);
69
+ function place() {
70
+ if (!boxRef.current) return;
71
+ const r = boxRef.current.getBoundingClientRect();
72
+ const maxH = 320, below = window.innerHeight - r.bottom - 8, up = below < 200 && r.top > below;
73
+ posState[1]({
74
+ left: r.left,
75
+ width: r.width,
76
+ top: up ? void 0 : r.bottom + 4,
77
+ bottom: up ? window.innerHeight - r.top + 4 : void 0,
78
+ maxHeight: Math.min(maxH, (up ? r.top : below) - 8)
79
+ });
80
+ }
81
+ useIsoLayoutEffect(
82
+ function() {
83
+ if (open) place();
84
+ },
85
+ [open, shown.length]
86
+ );
87
+ React.useEffect(
88
+ function() {
89
+ if (!open) return;
90
+ function outside(e) {
91
+ if (boxRef.current && boxRef.current.contains(e.target)) return;
92
+ if (listRef.current && listRef.current.contains(e.target)) return;
93
+ close(false);
94
+ }
95
+ function onScroll(e) {
96
+ if (!listRef.current || !listRef.current.contains(e.target)) place();
97
+ }
98
+ document.addEventListener("pointerdown", outside, true);
99
+ window.addEventListener("scroll", onScroll, true);
100
+ window.addEventListener("resize", place);
101
+ return function() {
102
+ document.removeEventListener("pointerdown", outside, true);
103
+ window.removeEventListener("scroll", onScroll, true);
104
+ window.removeEventListener("resize", place);
105
+ };
106
+ },
107
+ [open]
108
+ );
109
+ React.useEffect(
110
+ function() {
111
+ if (!open || !listRef.current) return;
112
+ const el = listRef.current.querySelector('[data-idx="' + activeIdx + '"]');
113
+ if (el && el.scrollIntoView) el.scrollIntoView({ block: "nearest" });
114
+ },
115
+ [activeIdx, open]
116
+ );
117
+ function openList() {
118
+ if (!open && !props.disabled && !props.readOnly) {
119
+ setOpen(true);
120
+ const i = selected ? shown.indexOf(selected) : 0;
121
+ setActive(i < 0 ? 0 : i);
73
122
  }
74
- function onScroll(e) {
75
- if (!listRef.current || !listRef.current.contains(e.target)) place();
123
+ }
124
+ function close(restoreLabel) {
125
+ setOpen(false);
126
+ setQuery(null);
127
+ }
128
+ function choose(o) {
129
+ if (!o || o.disabled) return;
130
+ if (multi) {
131
+ if (isPicked(o.value))
132
+ setValues(
133
+ values.filter(function(v) {
134
+ return v !== o.value;
135
+ })
136
+ );
137
+ else if (!full) setValues(values.concat([o.value]));
138
+ setQuery(null);
139
+ if (inputRef.current) inputRef.current.focus();
140
+ return;
76
141
  }
77
- document.addEventListener("pointerdown", outside, true);
78
- window.addEventListener("scroll", onScroll, true);
79
- window.addEventListener("resize", place);
80
- return function() {
81
- document.removeEventListener("pointerdown", outside, true);
82
- window.removeEventListener("scroll", onScroll, true);
83
- window.removeEventListener("resize", place);
84
- };
85
- },
86
- [open]
87
- );
88
- React.useEffect(
89
- function() {
90
- if (!open || !listRef.current) return;
91
- const el = listRef.current.querySelector('[data-idx="' + activeIdx + '"]');
92
- if (el && el.scrollIntoView) el.scrollIntoView({ block: "nearest" });
93
- },
94
- [activeIdx, open]
95
- );
96
- function openList() {
97
- if (!open && !props.disabled && !props.readOnly) {
98
- setOpen(true);
99
- const i = selected ? shown.indexOf(selected) : 0;
100
- setActive(i < 0 ? 0 : i);
142
+ setValue(o.value);
143
+ setQuery(null);
144
+ setOpen(false);
145
+ if (inputRef.current) inputRef.current.focus();
101
146
  }
102
- }
103
- function close(restoreLabel) {
104
- setOpen(false);
105
- setQuery(null);
106
- }
107
- function choose(o) {
108
- if (!o || o.disabled) return;
109
- setValue(o.value);
110
- setQuery(null);
111
- setOpen(false);
112
- if (inputRef.current) inputRef.current.focus();
113
- }
114
- function onKeyDown(e) {
115
- const k = e.key;
116
- if (k === "ArrowDown") {
117
- e.preventDefault();
118
- if (!open) openList();
119
- else setActive(Math.min(activeIdx + 1, shown.length - 1));
120
- } else if (k === "ArrowUp") {
121
- e.preventDefault();
122
- if (!open) openList();
123
- else setActive(Math.max(activeIdx - 1, 0));
124
- } else if (k === "Home" && open) {
125
- e.preventDefault();
126
- setActive(0);
127
- } else if (k === "End" && open) {
128
- e.preventDefault();
129
- setActive(shown.length - 1);
130
- } else if (k === "Enter") {
131
- if (open && shown[activeIdx]) {
147
+ function onKeyDown(e) {
148
+ const k = e.key;
149
+ if (k === "ArrowDown") {
132
150
  e.preventDefault();
133
- choose(shown[activeIdx]);
134
- }
135
- } else if (k === "Escape") {
136
- if (open) {
151
+ if (!open) openList();
152
+ else setActive(Math.min(activeIdx + 1, shown.length - 1));
153
+ } else if (k === "ArrowUp") {
154
+ e.preventDefault();
155
+ if (!open) openList();
156
+ else setActive(Math.max(activeIdx - 1, 0));
157
+ } else if (k === "Home" && open) {
137
158
  e.preventDefault();
138
- e.stopPropagation();
139
- close();
140
- } else if (props.clearable !== false && value != null && query == null) {
141
- setValue(null);
159
+ setActive(0);
160
+ } else if (k === "End" && open) {
161
+ e.preventDefault();
162
+ setActive(shown.length - 1);
163
+ } else if (k === "Enter") {
164
+ if (open && shown[activeIdx]) {
165
+ e.preventDefault();
166
+ choose(shown[activeIdx]);
167
+ }
168
+ } else if (k === "Escape") {
169
+ if (open) {
170
+ e.preventDefault();
171
+ e.stopPropagation();
172
+ close();
173
+ } else if (!multi && props.clearable !== false && value != null && query == null) {
174
+ setValue(null);
175
+ }
176
+ } else if (k === "Backspace" && multi && !text && values.length) {
177
+ setValues(values.slice(0, -1));
178
+ } else if (k === "Tab") {
179
+ if (open) close();
142
180
  }
143
- } else if (k === "Tab") {
144
- if (open) close();
145
181
  }
146
- }
147
- const text = query != null ? query : selected ? selected.label : "";
148
- const optId = function(i) {
149
- return id + "-opt-" + i;
150
- };
151
- const list = open && mounted && posState[0] ? createPortal(
152
- /* @__PURE__ */ React.createElement("div", { ref: listRef, className: "aura-combo__popover", style: posState[0] }, /* @__PURE__ */ React.createElement("ul", { id: listId, role: "listbox", "aria-label": props.label, className: "aura-combo__list" }, props.loading ? /* @__PURE__ */ React.createElement("li", { className: "aura-combo__note", role: "presentation" }, /* @__PURE__ */ React.createElement(Icon, { name: "loader-circle", className: "aura-spin" }), props.loadingText || t.searching) : !shown.length ? /* @__PURE__ */ React.createElement("li", { className: "aura-combo__note", role: "presentation" }, props.emptyText || t.noMatches) : shown.map(function(o, i) {
153
- const isSel = selected && o.value === selected.value;
154
- return /* @__PURE__ */ React.createElement(
155
- "li",
182
+ const text = query != null ? query : selected ? selected.label : "";
183
+ const summaryId = id + "-picked";
184
+ const optId = function(i) {
185
+ return id + "-opt-" + i;
186
+ };
187
+ const list = open && mounted && posState[0] ? createPortal(
188
+ /* @__PURE__ */ React.createElement("div", { ref: listRef, className: "aura-combo__popover", style: posState[0] }, /* @__PURE__ */ React.createElement(
189
+ "ul",
156
190
  {
157
- key: o.value,
158
- id: optId(i),
159
- role: "option",
160
- "data-idx": i,
161
- "aria-selected": isSel,
162
- "aria-disabled": o.disabled || void 0,
163
- className: cx(
164
- "aura-combo__option",
165
- i === activeIdx && "is-active",
166
- isSel && "is-selected",
167
- o.disabled && "is-disabled"
168
- ),
169
- onPointerDown: function(e) {
170
- e.preventDefault();
171
- },
172
- onClick: function() {
173
- choose(o);
174
- },
175
- onPointerMove: function() {
176
- if (activeIdx !== i) setActive(i);
177
- }
191
+ id: listId,
192
+ role: "listbox",
193
+ "aria-label": props.label,
194
+ "aria-multiselectable": multi || void 0,
195
+ className: "aura-combo__list"
178
196
  },
179
- o.icon ? /* @__PURE__ */ React.createElement(Icon, { name: o.icon }) : null,
180
- /* @__PURE__ */ React.createElement("span", { className: "aura-combo__text" }, /* @__PURE__ */ React.createElement("span", { className: "aura-combo__label" }, o.label), o.description ? /* @__PURE__ */ React.createElement("span", { className: "aura-combo__desc" }, o.description) : null),
181
- isSel ? /* @__PURE__ */ React.createElement(Icon, { name: "check", className: "aura-combo__check" }) : null
182
- );
183
- }), more && !props.loading ? /* @__PURE__ */ React.createElement("li", { className: "aura-combo__note", role: "presentation" }, t.keepTyping((props.options || []).length)) : null)),
184
- document.body
185
- ) : null;
186
- return /* @__PURE__ */ React.createElement(
187
- Field,
188
- {
189
- id,
190
- label: props.label,
191
- hint: props.hint,
192
- error: props.error,
193
- required: props.required,
194
- optional: props.optional,
195
- disabled: props.disabled,
196
- className: props.className
197
- },
198
- /* @__PURE__ */ React.createElement("div", { ref: boxRef, className: cx("aura-input aura-combo has-icon", open && "is-open") }, /* @__PURE__ */ React.createElement(Icon, { name: props.icon || "search", className: "aura-input__icon" }), /* @__PURE__ */ React.createElement(
199
- "input",
197
+ props.loading ? /* @__PURE__ */ React.createElement("li", { className: "aura-combo__note", role: "presentation" }, /* @__PURE__ */ React.createElement(Icon, { name: "loader-circle", className: "aura-spin" }), props.loadingText || t.searching) : !shown.length ? /* @__PURE__ */ React.createElement("li", { className: "aura-combo__note", role: "presentation" }, props.emptyText || t.noMatches) : shown.map(function(o, i) {
198
+ const isSel = multi ? isPicked(o.value) : !!selected && o.value === selected.value;
199
+ const blocked = o.disabled || multi && full && !isSel;
200
+ return /* @__PURE__ */ React.createElement(
201
+ "li",
202
+ {
203
+ key: o.value,
204
+ id: optId(i),
205
+ role: "option",
206
+ "data-idx": i,
207
+ "aria-selected": isSel,
208
+ "aria-disabled": blocked || void 0,
209
+ className: cx(
210
+ "aura-combo__option",
211
+ i === activeIdx && "is-active",
212
+ isSel && "is-selected",
213
+ blocked && "is-disabled"
214
+ ),
215
+ onPointerDown: function(e) {
216
+ e.preventDefault();
217
+ },
218
+ onClick: function() {
219
+ if (!blocked || isSel) choose(o);
220
+ },
221
+ onPointerMove: function() {
222
+ if (activeIdx !== i) setActive(i);
223
+ }
224
+ },
225
+ o.icon ? /* @__PURE__ */ React.createElement(Icon, { name: o.icon }) : null,
226
+ /* @__PURE__ */ React.createElement("span", { className: "aura-combo__text" }, /* @__PURE__ */ React.createElement("span", { className: "aura-combo__label" }, o.label), o.description ? /* @__PURE__ */ React.createElement("span", { className: "aura-combo__desc" }, o.description) : null),
227
+ isSel ? /* @__PURE__ */ React.createElement(Icon, { name: "check", className: "aura-combo__check" }) : null
228
+ );
229
+ }),
230
+ more && !props.loading ? /* @__PURE__ */ React.createElement("li", { className: "aura-combo__note", role: "presentation" }, t.keepTyping((props.options || []).length)) : null
231
+ )),
232
+ document.body
233
+ ) : null;
234
+ return /* @__PURE__ */ React.createElement(
235
+ Field,
200
236
  {
201
- ref: inputMerged,
202
237
  id,
203
- type: "text",
204
- role: "combobox",
205
- className: "aura-input__control",
206
- autoComplete: "off",
207
- "aria-expanded": open,
208
- "aria-controls": listId,
209
- "aria-autocomplete": "list",
210
- "aria-activedescendant": open && shown[activeIdx] ? optId(activeIdx) : void 0,
211
- "aria-invalid": props.error ? true : void 0,
212
- "aria-describedby": props.error ? id + "-error" : props.hint ? id + "-hint" : void 0,
213
- placeholder: props.placeholder,
214
- disabled: props.disabled,
215
- readOnly: props.readOnly,
238
+ label: props.label,
239
+ hint: props.hint,
240
+ error: props.error,
216
241
  required: props.required,
217
- name: props.name,
218
- value: text,
219
- onChange: function(e) {
220
- setQuery(e.target.value);
221
- setActive(0);
222
- if (!open) setOpen(true);
223
- if (props.onSearch) props.onSearch(e.target.value);
224
- },
225
- onClick: openList,
226
- onKeyDown,
227
- onBlur: function() {
228
- setTimeout(function() {
229
- if (listRef.current && listRef.current.contains(document.activeElement)) return;
230
- setOpen(false);
231
- setQuery(null);
232
- }, 0);
233
- }
234
- }
235
- ), props.clearable !== false && value != null && !props.disabled ? /* @__PURE__ */ React.createElement(
236
- "button",
237
- {
238
- type: "button",
239
- className: "aura-combo__clear",
240
- "aria-label": t.clear(props.label),
241
- tabIndex: -1,
242
- onClick: function() {
243
- setValue(null);
244
- setQuery(null);
245
- if (inputRef.current) inputRef.current.focus();
246
- }
242
+ optional: props.optional,
243
+ disabled: props.disabled,
244
+ className: props.className
247
245
  },
248
- /* @__PURE__ */ React.createElement(Icon, { name: "x" })
249
- ) : null, /* @__PURE__ */ React.createElement(
250
- "button",
251
- {
252
- type: "button",
253
- tabIndex: -1,
254
- "aria-hidden": true,
255
- className: "aura-combo__toggle",
256
- onPointerDown: function(e) {
257
- e.preventDefault();
258
- },
259
- onClick: function() {
260
- if (open) close();
261
- else {
262
- openList();
263
- inputRef.current && inputRef.current.focus();
246
+ /* @__PURE__ */ React.createElement("div", { ref: boxRef, className: cx("aura-input aura-combo has-icon", open && "is-open", multi && "is-multi") }, /* @__PURE__ */ React.createElement(Icon, { name: props.icon || "search", className: "aura-input__icon" }), multi && picked.length ? /* @__PURE__ */ React.createElement("span", { className: "aura-combo__chips" }, picked.map(function(o) {
247
+ return /* @__PURE__ */ React.createElement("span", { key: o.value, className: "aura-combo__chip" }, /* @__PURE__ */ React.createElement("span", { className: "aura-combo__chip-label" }, o.label), props.disabled || props.readOnly ? null : /* @__PURE__ */ React.createElement(
248
+ "button",
249
+ {
250
+ type: "button",
251
+ tabIndex: -1,
252
+ className: "aura-combo__chip-remove",
253
+ "aria-label": t.remove(o.label),
254
+ onPointerDown: function(e) {
255
+ e.preventDefault();
256
+ },
257
+ onClick: function() {
258
+ setValues(
259
+ values.filter(function(v) {
260
+ return v !== o.value;
261
+ })
262
+ );
263
+ if (inputRef.current) inputRef.current.focus();
264
+ }
265
+ },
266
+ /* @__PURE__ */ React.createElement(Icon, { name: "x" })
267
+ ));
268
+ })) : null, multi ? /* @__PURE__ */ React.createElement("span", { id: summaryId, className: "aura-sr-only" }, picked.length ? t.selectedCount(picked.length) + ": " + picked.map(function(o) {
269
+ return o.label;
270
+ }).join(", ") : "") : null, multi && props.name ? values.map(function(v) {
271
+ return /* @__PURE__ */ React.createElement("input", { key: v, type: "hidden", name: props.name, value: v });
272
+ }) : null, /* @__PURE__ */ React.createElement(
273
+ "input",
274
+ {
275
+ ref: inputMerged,
276
+ id,
277
+ type: "text",
278
+ role: "combobox",
279
+ className: "aura-input__control",
280
+ autoComplete: "off",
281
+ "aria-expanded": open,
282
+ "aria-controls": listId,
283
+ "aria-autocomplete": "list",
284
+ "aria-activedescendant": open && shown[activeIdx] ? optId(activeIdx) : void 0,
285
+ "aria-invalid": props.error ? true : void 0,
286
+ "aria-describedby": [props.error ? id + "-error" : props.hint ? id + "-hint" : "", multi && picked.length ? summaryId : ""].filter(Boolean).join(" ") || void 0,
287
+ placeholder: multi && values.length ? void 0 : props.placeholder,
288
+ disabled: props.disabled,
289
+ readOnly: props.readOnly,
290
+ required: props.required && (!multi || !values.length),
291
+ name: multi ? void 0 : props.name,
292
+ value: text,
293
+ onChange: function(e) {
294
+ setQuery(e.target.value);
295
+ setActive(0);
296
+ if (!open) setOpen(true);
297
+ if (props.onSearch) props.onSearch(e.target.value);
298
+ },
299
+ onClick: openList,
300
+ onKeyDown,
301
+ onBlur: function() {
302
+ setTimeout(function() {
303
+ if (listRef.current && listRef.current.contains(document.activeElement)) return;
304
+ setOpen(false);
305
+ setQuery(null);
306
+ }, 0);
264
307
  }
265
308
  }
266
- },
267
- /* @__PURE__ */ React.createElement(Icon, { name: "chevron-down" })
268
- )),
269
- list
270
- );
271
- });
309
+ ), props.clearable !== false && values.length && !props.disabled ? /* @__PURE__ */ React.createElement(
310
+ "button",
311
+ {
312
+ type: "button",
313
+ className: "aura-combo__clear",
314
+ "aria-label": t.clear(props.label),
315
+ tabIndex: -1,
316
+ onClick: function() {
317
+ setValues([]);
318
+ setQuery(null);
319
+ if (inputRef.current) inputRef.current.focus();
320
+ }
321
+ },
322
+ /* @__PURE__ */ React.createElement(Icon, { name: "x" })
323
+ ) : null, /* @__PURE__ */ React.createElement(
324
+ "button",
325
+ {
326
+ type: "button",
327
+ tabIndex: -1,
328
+ "aria-hidden": true,
329
+ className: "aura-combo__toggle",
330
+ onPointerDown: function(e) {
331
+ e.preventDefault();
332
+ },
333
+ onClick: function() {
334
+ if (open) close();
335
+ else {
336
+ openList();
337
+ inputRef.current && inputRef.current.focus();
338
+ }
339
+ }
340
+ },
341
+ /* @__PURE__ */ React.createElement(Icon, { name: "chevron-down" })
342
+ )),
343
+ list
344
+ );
345
+ }
346
+ );
272
347
  export {
273
348
  Combobox,
274
349
  defaultFilter