@jirawatpyk/aura-react 5.2.0 → 5.3.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/README.md +8 -1
- package/dist/aura.bundle.js +495 -39
- package/dist/cjs/index.cjs +495 -39
- package/dist/esm/Combobox.js +3 -2
- package/dist/esm/Select.js +471 -15
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/styles.css +9 -0
- package/dist/styles.layer.css +9 -0
- package/package.json +1 -1
package/dist/esm/Combobox.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import { useStrings } from "./locale.js";
|
|
3
|
+
import { useStrings, useDensity } from "./locale.js";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
5
|
import { cx, uid, useMaybeControlled, useMounted, useIsoLayoutEffect, useMergedRef } from "./internal.js";
|
|
6
6
|
import { Icon } from "./Icon.js";
|
|
@@ -24,6 +24,7 @@ const Combobox = React.forwardRef(
|
|
|
24
24
|
const multi = all.multiple === true;
|
|
25
25
|
const mp = all;
|
|
26
26
|
const t = useStrings();
|
|
27
|
+
const density = useDensity();
|
|
27
28
|
const auto = uid(), id = props.id || auto, listId = id + "-list";
|
|
28
29
|
const options = (props.options || []).map(toOpt);
|
|
29
30
|
const st = useMaybeControlled(
|
|
@@ -187,7 +188,7 @@ const Combobox = React.forwardRef(
|
|
|
187
188
|
return id + "-opt-" + i;
|
|
188
189
|
};
|
|
189
190
|
const list = open && mounted && posState[0] ? createPortal(
|
|
190
|
-
/* @__PURE__ */ React.createElement("div", { ref: listRef, className: "aura-combo__popover", style: posState[0] }, live.length ? /* @__PURE__ */ React.createElement(
|
|
191
|
+
/* @__PURE__ */ React.createElement("div", { ref: listRef, "data-density": density, className: "aura-combo__popover", style: posState[0] }, live.length ? /* @__PURE__ */ React.createElement(
|
|
191
192
|
"ul",
|
|
192
193
|
{
|
|
193
194
|
id: listId,
|
package/dist/esm/Select.js
CHANGED
|
@@ -1,9 +1,55 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import * as React from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
3
4
|
import { FIELD_KEYS, Field, describedBy } from "./Field.js";
|
|
4
5
|
import { Icon } from "./Icon.js";
|
|
5
|
-
import {
|
|
6
|
+
import { useDensity } from "./locale.js";
|
|
7
|
+
import { cx, omit, uid, useIsoLayoutEffect, useMergedRef, useMounted } from "./internal.js";
|
|
6
8
|
const h = React.createElement;
|
|
9
|
+
const FORM_KEYS = ["name", "form", "value", "defaultValue", "disabled", "autoComplete", "onInput", "onInvalid"];
|
|
10
|
+
const HANDLED_KEYS = FORM_KEYS.concat([
|
|
11
|
+
"onChange",
|
|
12
|
+
"onBlur",
|
|
13
|
+
"onFocus",
|
|
14
|
+
"onKeyDown",
|
|
15
|
+
"onClick",
|
|
16
|
+
"autoFocus",
|
|
17
|
+
"tabIndex",
|
|
18
|
+
"aria-label",
|
|
19
|
+
"aria-labelledby",
|
|
20
|
+
"aria-describedby",
|
|
21
|
+
"multiple",
|
|
22
|
+
"size"
|
|
23
|
+
]);
|
|
24
|
+
function pick(o, keys) {
|
|
25
|
+
const r = {};
|
|
26
|
+
for (const k of keys) if (k in o) r[k] = o[k];
|
|
27
|
+
return r;
|
|
28
|
+
}
|
|
29
|
+
function readItems(el) {
|
|
30
|
+
if (!el) return [];
|
|
31
|
+
return Array.prototype.map.call(el.options, function(o, i) {
|
|
32
|
+
const g = o.parentElement && o.parentElement.tagName === "OPTGROUP" ? o.parentElement : null;
|
|
33
|
+
return {
|
|
34
|
+
value: o.value,
|
|
35
|
+
label: o.textContent || "",
|
|
36
|
+
disabled: o.disabled || !!(g && g.disabled),
|
|
37
|
+
group: g ? g.label : null,
|
|
38
|
+
index: i
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function initialLabel(props) {
|
|
43
|
+
const opts = (props.options || []).map(function(o) {
|
|
44
|
+
return typeof o === "object" ? o : { value: o, label: o };
|
|
45
|
+
});
|
|
46
|
+
const v = props.value !== void 0 && props.value !== null ? String(props.value) : props.defaultValue !== void 0 && props.defaultValue !== null ? String(props.defaultValue) : props.placeholder ? "" : opts[0] ? opts[0].value : "";
|
|
47
|
+
const hit = opts.filter(function(o) {
|
|
48
|
+
return o.value === v;
|
|
49
|
+
})[0];
|
|
50
|
+
if (hit) return { label: hit.label, empty: false };
|
|
51
|
+
return { label: props.placeholder || "", empty: true };
|
|
52
|
+
}
|
|
7
53
|
const Select = React.forwardRef(function Select2(props, ref) {
|
|
8
54
|
const auto = uid(), id = props.id || auto;
|
|
9
55
|
const rest = omit(props, FIELD_KEYS.concat(["children"]));
|
|
@@ -16,10 +62,259 @@ const Select = React.forwardRef(function Select2(props, ref) {
|
|
|
16
62
|
/* @__PURE__ */ React.createElement("option", { key: "__ph", value: "", disabled: true }, props.placeholder)
|
|
17
63
|
);
|
|
18
64
|
const extra = props.value === void 0 && props.defaultValue === void 0 && props.placeholder ? { defaultValue: "" } : {};
|
|
19
|
-
|
|
65
|
+
const native = !!props.multiple || props.size != null && props.size > 1;
|
|
66
|
+
const mounted = useMounted();
|
|
67
|
+
const live = !native && mounted;
|
|
68
|
+
const density = useDensity();
|
|
69
|
+
const selRef = React.useRef(null), trigRef = React.useRef(null), listRef = React.useRef(null);
|
|
70
|
+
const shown = React.useState(function() {
|
|
71
|
+
return initialLabel(props);
|
|
72
|
+
});
|
|
73
|
+
const openState = React.useState(false), open = openState[0];
|
|
74
|
+
const activeState = React.useState(-1), active = activeState[0];
|
|
75
|
+
const items = React.useState([]);
|
|
76
|
+
const pos = React.useState(null);
|
|
77
|
+
const typed = React.useRef({ text: "", at: 0 });
|
|
78
|
+
const sync = React.useCallback(function() {
|
|
79
|
+
const el = selRef.current;
|
|
80
|
+
if (!el) return;
|
|
81
|
+
const o = el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null;
|
|
82
|
+
const next = o ? { label: o.textContent || "", empty: o.value === "" } : { label: "", empty: true };
|
|
83
|
+
shown[1](function(cur) {
|
|
84
|
+
return cur.label === next.label && cur.empty === next.empty ? cur : next;
|
|
85
|
+
});
|
|
86
|
+
}, []);
|
|
87
|
+
const hook = React.useCallback(
|
|
88
|
+
function(el) {
|
|
89
|
+
selRef.current = el;
|
|
90
|
+
if (!el || native || el.__auraHooked) return;
|
|
91
|
+
el.__auraHooked = true;
|
|
92
|
+
const proto = HTMLSelectElement.prototype;
|
|
93
|
+
const pv = Object.getOwnPropertyDescriptor(proto, "value"), pi = Object.getOwnPropertyDescriptor(proto, "selectedIndex");
|
|
94
|
+
if (pv && pv.set && pv.get)
|
|
95
|
+
Object.defineProperty(el, "value", {
|
|
96
|
+
configurable: true,
|
|
97
|
+
get: function() {
|
|
98
|
+
return pv.get.call(el);
|
|
99
|
+
},
|
|
100
|
+
set: function(v) {
|
|
101
|
+
pv.set.call(el, v);
|
|
102
|
+
sync();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
if (pi && pi.set && pi.get)
|
|
106
|
+
Object.defineProperty(el, "selectedIndex", {
|
|
107
|
+
configurable: true,
|
|
108
|
+
get: function() {
|
|
109
|
+
return pi.get.call(el);
|
|
110
|
+
},
|
|
111
|
+
set: function(v) {
|
|
112
|
+
pi.set.call(el, v);
|
|
113
|
+
sync();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
el.focus = function(o) {
|
|
117
|
+
if (trigRef.current) trigRef.current.focus(o);
|
|
118
|
+
else proto.focus.call(el, o);
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
[native, sync]
|
|
122
|
+
);
|
|
123
|
+
const selMerged = useMergedRef(ref, hook);
|
|
124
|
+
React.useEffect(
|
|
125
|
+
function() {
|
|
126
|
+
const f = selRef.current && selRef.current.form;
|
|
127
|
+
if (!f || native) return;
|
|
128
|
+
function onReset() {
|
|
129
|
+
setTimeout(sync, 0);
|
|
130
|
+
}
|
|
131
|
+
f.addEventListener("reset", onReset);
|
|
132
|
+
return function() {
|
|
133
|
+
f.removeEventListener("reset", onReset);
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
[native, sync]
|
|
137
|
+
);
|
|
138
|
+
const focusedOnce = React.useRef(false);
|
|
139
|
+
React.useEffect(
|
|
140
|
+
function() {
|
|
141
|
+
if (!live || focusedOnce.current || !trigRef.current) return;
|
|
142
|
+
focusedOnce.current = true;
|
|
143
|
+
if (props.autoFocus || selRef.current && document.activeElement === selRef.current) trigRef.current.focus();
|
|
144
|
+
},
|
|
145
|
+
[live]
|
|
146
|
+
);
|
|
147
|
+
useIsoLayoutEffect(function() {
|
|
148
|
+
if (!live) return;
|
|
149
|
+
sync();
|
|
150
|
+
if (!open) return;
|
|
151
|
+
const now = readItems(selRef.current), was = items[0];
|
|
152
|
+
const same = now.length === was.length && now.every(function(it, i) {
|
|
153
|
+
const w = was[i];
|
|
154
|
+
return w.value === it.value && w.label === it.label && w.disabled === it.disabled && w.group === it.group;
|
|
155
|
+
});
|
|
156
|
+
if (same) return;
|
|
157
|
+
items[1](now);
|
|
158
|
+
if (!choices(now).length) {
|
|
159
|
+
close(false);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const hv = was[active] ? was[active].value : null;
|
|
163
|
+
const again = now.filter(function(it) {
|
|
164
|
+
return !it.disabled && it.value === hv;
|
|
165
|
+
})[0];
|
|
166
|
+
const cur = selRef.current ? selRef.current.selectedIndex : -1;
|
|
167
|
+
activeState[1](again ? again.index : now[cur] && !now[cur].disabled ? cur : choices(now)[0].index);
|
|
168
|
+
});
|
|
169
|
+
function choices(list) {
|
|
170
|
+
return list.filter(function(i) {
|
|
171
|
+
return !i.disabled;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function choose(it) {
|
|
175
|
+
const el = selRef.current;
|
|
176
|
+
if (!el || it.disabled) return;
|
|
177
|
+
if (el.value !== it.value) {
|
|
178
|
+
el.value = it.value;
|
|
179
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
180
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
181
|
+
}
|
|
182
|
+
sync();
|
|
183
|
+
close(true);
|
|
184
|
+
}
|
|
185
|
+
function openList(at) {
|
|
186
|
+
if (props.disabled || trigRef.current && trigRef.current.disabled) return;
|
|
187
|
+
const list = readItems(selRef.current);
|
|
188
|
+
const ok = choices(list);
|
|
189
|
+
if (!ok.length) return;
|
|
190
|
+
items[1](list);
|
|
191
|
+
const cur = selRef.current ? selRef.current.selectedIndex : -1;
|
|
192
|
+
let a = at === "first" ? ok[0].index : at === "last" ? ok[ok.length - 1].index : cur;
|
|
193
|
+
if (typeof at === "number") a = at;
|
|
194
|
+
if (a < 0 || !list[a] || list[a].disabled) a = ok[0].index;
|
|
195
|
+
activeState[1](a);
|
|
196
|
+
openState[1](true);
|
|
197
|
+
}
|
|
198
|
+
function close(focus) {
|
|
199
|
+
openState[1](false);
|
|
200
|
+
pos[1](null);
|
|
201
|
+
if (focus && trigRef.current) trigRef.current.focus();
|
|
202
|
+
}
|
|
203
|
+
function step(from, dir, list) {
|
|
204
|
+
for (let i = from + dir; i >= 0 && i < list.length; i += dir) if (!list[i].disabled) return i;
|
|
205
|
+
return from;
|
|
206
|
+
}
|
|
207
|
+
function typing() {
|
|
208
|
+
return !!typed.current.text && Date.now() - typed.current.at < 500;
|
|
209
|
+
}
|
|
210
|
+
function typeahead(ch, list, from) {
|
|
211
|
+
const now = Date.now(), t = typed.current;
|
|
212
|
+
t.text = now - t.at < 500 ? t.text + ch : ch;
|
|
213
|
+
t.at = now;
|
|
214
|
+
const q = t.text.toLocaleLowerCase();
|
|
215
|
+
for (let k = 1; k <= list.length; k++) {
|
|
216
|
+
const i = (from + (t.text.length > 1 ? 0 : 1) + k - 1 + list.length) % list.length;
|
|
217
|
+
if (!list[i].disabled && list[i].label.toLocaleLowerCase().indexOf(q) === 0) return i;
|
|
218
|
+
}
|
|
219
|
+
t.text = "";
|
|
220
|
+
return -1;
|
|
221
|
+
}
|
|
222
|
+
function onKeyDown(e) {
|
|
223
|
+
const k = e.key;
|
|
224
|
+
const printable = k.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey;
|
|
225
|
+
const search = printable && (k !== " " || typing());
|
|
226
|
+
if (!open) {
|
|
227
|
+
if (search) {
|
|
228
|
+
const list2 = readItems(selRef.current);
|
|
229
|
+
const i = typeahead(k, list2, selRef.current ? selRef.current.selectedIndex : -1);
|
|
230
|
+
e.preventDefault();
|
|
231
|
+
if (i >= 0) openList(i);
|
|
232
|
+
} else if (k === "ArrowDown" || k === "ArrowUp" || k === "Enter" || k === " ") {
|
|
233
|
+
e.preventDefault();
|
|
234
|
+
openList(k === "ArrowUp" && !(selRef.current && selRef.current.value) ? "last" : void 0);
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const list = items[0];
|
|
239
|
+
if (search) {
|
|
240
|
+
e.preventDefault();
|
|
241
|
+
const i = typeahead(k, list, active);
|
|
242
|
+
if (i >= 0) activeState[1](i);
|
|
243
|
+
} else if (k === "ArrowDown") {
|
|
244
|
+
e.preventDefault();
|
|
245
|
+
activeState[1](step(active, 1, list));
|
|
246
|
+
} else if (k === "ArrowUp") {
|
|
247
|
+
e.preventDefault();
|
|
248
|
+
activeState[1](step(active, -1, list));
|
|
249
|
+
} else if (k === "Home" || k === "PageUp") {
|
|
250
|
+
e.preventDefault();
|
|
251
|
+
activeState[1](k === "Home" ? step(-1, 1, list) : Math.max(step(-1, 1, list), step(active - 9, 1, list)));
|
|
252
|
+
} else if (k === "End" || k === "PageDown") {
|
|
253
|
+
e.preventDefault();
|
|
254
|
+
activeState[1](
|
|
255
|
+
k === "End" ? step(list.length, -1, list) : Math.min(step(list.length, -1, list), step(active + 9, -1, list))
|
|
256
|
+
);
|
|
257
|
+
} else if (k === "Enter" || k === " ") {
|
|
258
|
+
e.preventDefault();
|
|
259
|
+
if (list[active]) choose(list[active]);
|
|
260
|
+
} else if (k === "Escape") {
|
|
261
|
+
e.preventDefault();
|
|
262
|
+
e.stopPropagation();
|
|
263
|
+
close(true);
|
|
264
|
+
} else if (k === "Tab") {
|
|
265
|
+
close(false);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
useIsoLayoutEffect(
|
|
269
|
+
function() {
|
|
270
|
+
if (!open) return;
|
|
271
|
+
function place() {
|
|
272
|
+
const t = trigRef.current && trigRef.current.parentElement;
|
|
273
|
+
if (!t) return;
|
|
274
|
+
const r = t.getBoundingClientRect(), below = window.innerHeight - r.bottom - 8, above = r.top - 8, want = listRef.current ? listRef.current.scrollHeight : 320, up = below < Math.min(want, 200) && above > below;
|
|
275
|
+
pos[1]({
|
|
276
|
+
left: r.left,
|
|
277
|
+
width: r.width,
|
|
278
|
+
top: up ? void 0 : r.bottom + 4,
|
|
279
|
+
bottom: up ? window.innerHeight - r.top + 4 : void 0,
|
|
280
|
+
maxHeight: Math.min(320, (up ? above : below) - 4)
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
place();
|
|
284
|
+
function outside(e) {
|
|
285
|
+
const n = e.target;
|
|
286
|
+
if (listRef.current && listRef.current.contains(n)) return;
|
|
287
|
+
if (trigRef.current && trigRef.current.contains(n)) return;
|
|
288
|
+
close(false);
|
|
289
|
+
}
|
|
290
|
+
function onScroll(e) {
|
|
291
|
+
if (!listRef.current || !listRef.current.contains(e.target)) place();
|
|
292
|
+
}
|
|
293
|
+
document.addEventListener("pointerdown", outside, true);
|
|
294
|
+
window.addEventListener("scroll", onScroll, true);
|
|
295
|
+
window.addEventListener("resize", place);
|
|
296
|
+
return function() {
|
|
297
|
+
document.removeEventListener("pointerdown", outside, true);
|
|
298
|
+
window.removeEventListener("scroll", onScroll, true);
|
|
299
|
+
window.removeEventListener("resize", place);
|
|
300
|
+
};
|
|
301
|
+
},
|
|
302
|
+
[open]
|
|
303
|
+
);
|
|
304
|
+
React.useEffect(
|
|
305
|
+
function() {
|
|
306
|
+
if (!open || !listRef.current || active < 0) return;
|
|
307
|
+
const el = listRef.current.querySelector('[data-idx="' + active + '"]');
|
|
308
|
+
if (el && el.scrollIntoView) el.scrollIntoView({ block: "nearest" });
|
|
309
|
+
},
|
|
310
|
+
[open, active, pos[0] != null]
|
|
311
|
+
);
|
|
312
|
+
const described = [describedBy(id, props), rest["aria-describedby"]].filter(Boolean).join(" ") || void 0;
|
|
313
|
+
const field = (child) => /* @__PURE__ */ React.createElement(
|
|
20
314
|
Field,
|
|
21
315
|
{
|
|
22
316
|
id,
|
|
317
|
+
labelId: native ? void 0 : id + "-label",
|
|
23
318
|
label: props.label,
|
|
24
319
|
hint: props.hint,
|
|
25
320
|
error: props.error,
|
|
@@ -28,19 +323,180 @@ const Select = React.forwardRef(function Select2(props, ref) {
|
|
|
28
323
|
disabled: props.disabled,
|
|
29
324
|
className: props.className
|
|
30
325
|
},
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
326
|
+
child
|
|
327
|
+
);
|
|
328
|
+
if (!live)
|
|
329
|
+
return field(
|
|
330
|
+
/* @__PURE__ */ React.createElement("div", { className: cx("aura-input aura-select", props.icon && "has-icon") }, props.icon ? /* @__PURE__ */ React.createElement(Icon, { name: props.icon, className: "aura-input__icon" }) : null, h(
|
|
331
|
+
"select",
|
|
332
|
+
Object.assign(extra, rest, {
|
|
333
|
+
ref: native ? ref : selMerged,
|
|
334
|
+
id,
|
|
335
|
+
className: "aura-input__control",
|
|
336
|
+
required: props.required,
|
|
337
|
+
"aria-invalid": props.error ? true : void 0,
|
|
338
|
+
"aria-describedby": described
|
|
339
|
+
}),
|
|
340
|
+
opts,
|
|
341
|
+
props.children
|
|
342
|
+
), /* @__PURE__ */ React.createElement(Icon, { name: "chevron-down", className: "aura-select__chevron" }))
|
|
343
|
+
);
|
|
344
|
+
const listId = id + "-list", labelId = id + "-label";
|
|
345
|
+
const optId = function(i) {
|
|
346
|
+
return id + "-opt-" + i;
|
|
347
|
+
};
|
|
348
|
+
const labelledBy = rest["aria-labelledby"] || (props.label ? labelId : void 0);
|
|
349
|
+
const selIndex = selRef.current ? selRef.current.selectedIndex : -1;
|
|
350
|
+
const option = function(it) {
|
|
351
|
+
const sel = selIndex === it.index;
|
|
352
|
+
return /* @__PURE__ */ React.createElement(
|
|
353
|
+
"div",
|
|
354
|
+
{
|
|
355
|
+
key: it.index,
|
|
356
|
+
id: optId(it.index),
|
|
357
|
+
role: "option",
|
|
358
|
+
"data-idx": it.index,
|
|
359
|
+
"aria-selected": sel,
|
|
360
|
+
"aria-disabled": it.disabled || void 0,
|
|
361
|
+
className: cx(
|
|
362
|
+
"aura-combo__option",
|
|
363
|
+
it.index === active && "is-active",
|
|
364
|
+
sel && "is-selected",
|
|
365
|
+
it.disabled && "is-disabled"
|
|
366
|
+
),
|
|
367
|
+
onPointerDown: function(e) {
|
|
368
|
+
e.preventDefault();
|
|
369
|
+
},
|
|
370
|
+
onPointerMove: function() {
|
|
371
|
+
if (!it.disabled && active !== it.index) activeState[1](it.index);
|
|
372
|
+
},
|
|
373
|
+
onClick: function() {
|
|
374
|
+
choose(it);
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
/* @__PURE__ */ React.createElement("span", { className: "aura-combo__text" }, /* @__PURE__ */ React.createElement("span", { className: "aura-combo__label" }, it.label)),
|
|
378
|
+
sel ? /* @__PURE__ */ React.createElement(Icon, { name: "check", className: "aura-combo__check" }) : null
|
|
379
|
+
);
|
|
380
|
+
};
|
|
381
|
+
const runs = [];
|
|
382
|
+
items[0].forEach(function(it) {
|
|
383
|
+
if (it.value === "" && it.disabled) return;
|
|
384
|
+
const last = runs[runs.length - 1];
|
|
385
|
+
if (last && last.group === it.group) last.items.push(it);
|
|
386
|
+
else runs.push({ group: it.group, items: [it] });
|
|
387
|
+
});
|
|
388
|
+
const popup = open ? createPortal(
|
|
389
|
+
/* @__PURE__ */ React.createElement(
|
|
390
|
+
"div",
|
|
391
|
+
{
|
|
392
|
+
ref: listRef,
|
|
393
|
+
"data-density": density,
|
|
394
|
+
className: "aura-combo__popover aura-select__popover",
|
|
395
|
+
style: pos[0] ? {
|
|
396
|
+
left: pos[0].left,
|
|
397
|
+
minWidth: pos[0].width,
|
|
398
|
+
top: pos[0].top,
|
|
399
|
+
bottom: pos[0].bottom,
|
|
400
|
+
maxHeight: pos[0].maxHeight
|
|
401
|
+
} : { left: -9999, top: -9999 }
|
|
402
|
+
},
|
|
403
|
+
/* @__PURE__ */ React.createElement(
|
|
404
|
+
"div",
|
|
405
|
+
{
|
|
406
|
+
id: listId,
|
|
407
|
+
role: "listbox",
|
|
408
|
+
"aria-labelledby": labelledBy,
|
|
409
|
+
"aria-label": labelledBy ? void 0 : rest["aria-label"],
|
|
410
|
+
className: "aura-combo__list"
|
|
411
|
+
},
|
|
412
|
+
runs.map(function(r, n) {
|
|
413
|
+
if (r.group === null) return /* @__PURE__ */ React.createElement(React.Fragment, { key: "r" + n }, r.items.map(option));
|
|
414
|
+
const gid = id + "-group-" + n;
|
|
415
|
+
return /* @__PURE__ */ React.createElement("div", { key: "r" + n, role: "group", "aria-labelledby": gid }, /* @__PURE__ */ React.createElement("div", { id: gid, className: "aura-select__group" }, r.group), r.items.map(option));
|
|
416
|
+
})
|
|
417
|
+
)
|
|
418
|
+
),
|
|
419
|
+
document.body
|
|
420
|
+
) : null;
|
|
421
|
+
const userFocus = rest.onFocus, userBlur = rest.onBlur, userKey = rest.onKeyDown, userClick = rest.onClick;
|
|
422
|
+
return field(
|
|
423
|
+
/* @__PURE__ */ React.createElement(
|
|
424
|
+
"div",
|
|
425
|
+
{
|
|
426
|
+
className: cx("aura-input aura-select aura-select--custom", props.icon && "has-icon", open && "is-open"),
|
|
427
|
+
"data-invalid": props.error ? "" : void 0
|
|
428
|
+
},
|
|
429
|
+
props.icon ? /* @__PURE__ */ React.createElement(Icon, { name: props.icon, className: "aura-input__icon" }) : null,
|
|
430
|
+
h(
|
|
431
|
+
"select",
|
|
432
|
+
Object.assign(extra, pick(rest, FORM_KEYS), {
|
|
433
|
+
ref: selMerged,
|
|
434
|
+
id: id + "-select",
|
|
435
|
+
className: "aura-select__native",
|
|
436
|
+
tabIndex: -1,
|
|
437
|
+
"aria-hidden": true,
|
|
438
|
+
required: props.required,
|
|
439
|
+
onChange: function(e) {
|
|
440
|
+
sync();
|
|
441
|
+
if (props.onChange) props.onChange(e);
|
|
442
|
+
},
|
|
443
|
+
/* A browser focusing the <select> (the required bubble, a label click) hands focus to the button. */
|
|
444
|
+
onFocus: function() {
|
|
445
|
+
if (trigRef.current) trigRef.current.focus();
|
|
446
|
+
},
|
|
447
|
+
/* Only the blur the button reports (below): the <select>'s own, while it hands focus over, is not one. */
|
|
448
|
+
onBlur: function(e) {
|
|
449
|
+
if (!e.nativeEvent.isTrusted && userBlur) userBlur(e);
|
|
450
|
+
}
|
|
451
|
+
}),
|
|
452
|
+
opts,
|
|
453
|
+
props.children
|
|
454
|
+
),
|
|
455
|
+
/* @__PURE__ */ React.createElement(
|
|
456
|
+
"button",
|
|
457
|
+
{
|
|
458
|
+
...omit(rest, HANDLED_KEYS),
|
|
459
|
+
ref: trigRef,
|
|
460
|
+
type: "button",
|
|
461
|
+
role: "combobox",
|
|
462
|
+
form: id + "-no-form",
|
|
463
|
+
className: "aura-input__control aura-select__trigger",
|
|
464
|
+
disabled: props.disabled,
|
|
465
|
+
tabIndex: rest.tabIndex,
|
|
466
|
+
"aria-haspopup": "listbox",
|
|
467
|
+
"aria-expanded": open,
|
|
468
|
+
"aria-controls": open ? listId : void 0,
|
|
469
|
+
"aria-activedescendant": open && active >= 0 ? optId(active) : void 0,
|
|
470
|
+
id,
|
|
471
|
+
"aria-label": rest["aria-label"],
|
|
472
|
+
"aria-labelledby": rest["aria-labelledby"],
|
|
473
|
+
"aria-required": props.required || void 0,
|
|
474
|
+
"aria-invalid": props.error ? true : void 0,
|
|
475
|
+
"aria-describedby": described,
|
|
476
|
+
onFocus: function(e) {
|
|
477
|
+
if (userFocus) userFocus(e);
|
|
478
|
+
},
|
|
479
|
+
onClick: function(e) {
|
|
480
|
+
if (userClick) userClick(e);
|
|
481
|
+
if (e.defaultPrevented) return;
|
|
482
|
+
if (open) close(true);
|
|
483
|
+
else openList();
|
|
484
|
+
},
|
|
485
|
+
onKeyDown: function(e) {
|
|
486
|
+
if (userKey) userKey(e);
|
|
487
|
+
if (!e.defaultPrevented) onKeyDown(e);
|
|
488
|
+
},
|
|
489
|
+
onBlur: function(e) {
|
|
490
|
+
if (listRef.current && e.relatedTarget && listRef.current.contains(e.relatedTarget)) return;
|
|
491
|
+
if (open) close(false);
|
|
492
|
+
if (selRef.current) selRef.current.dispatchEvent(new FocusEvent("focusout", { bubbles: true }));
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
/* @__PURE__ */ React.createElement("span", { id: id + "-value", className: cx("aura-select__value", shown[0].empty && "is-placeholder") }, shown[0].label || " ")
|
|
496
|
+
),
|
|
497
|
+
/* @__PURE__ */ React.createElement(Icon, { name: "chevron-down", className: "aura-select__chevron" }),
|
|
498
|
+
popup
|
|
499
|
+
)
|
|
44
500
|
);
|
|
45
501
|
});
|
|
46
502
|
export {
|
package/dist/index.d.cts
CHANGED
|
@@ -417,9 +417,12 @@ export type SelectOption = string | {
|
|
|
417
417
|
label: string;
|
|
418
418
|
disabled?: boolean | undefined;
|
|
419
419
|
};
|
|
420
|
+
/** 5.3: opens AURA's own list (light and dark alike) over a real `<select>`, which keeps `name`, `required`, the ref,
|
|
421
|
+
* `onChange`, react-hook-form `register` and form posts. `multiple` or `size > 1` keeps the native list box. */
|
|
420
422
|
export interface SelectProps extends FieldProps, Omit<React$1.SelectHTMLAttributes<HTMLSelectElement>, "required"> {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
+
/** The choices; or pass `<option>` / `<optgroup>` children instead (5.3: optional). */
|
|
424
|
+
options?: SelectOption[] | undefined;
|
|
425
|
+
/** Shown in fg-tertiary on the closed field until something is chosen; not listed as a choice. */
|
|
423
426
|
placeholder?: string | undefined;
|
|
424
427
|
icon?: IconInput | undefined;
|
|
425
428
|
}
|
|
@@ -1217,6 +1220,7 @@ export interface FieldComponentProps extends Omit<FieldPropsPublic, "label"> {
|
|
|
1217
1220
|
export declare const Field: React$1.ForwardRefExoticComponent<FieldComponentProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
1218
1221
|
export declare const TextField: React$1.ForwardRefExoticComponent<TextFieldProps & React$1.RefAttributes<HTMLInputElement>>;
|
|
1219
1222
|
export declare const Textarea: React$1.ForwardRefExoticComponent<TextareaProps & React$1.RefAttributes<HTMLTextAreaElement>>;
|
|
1223
|
+
/** A select field: a button that opens an AURA list (5.3), over a real <select> that keeps forms, refs and events. */
|
|
1220
1224
|
export declare const Select: React$1.ForwardRefExoticComponent<SelectProps & React$1.RefAttributes<HTMLSelectElement>>;
|
|
1221
1225
|
export declare const RadioGroup: React$1.ForwardRefExoticComponent<RadioGroupProps & React$1.RefAttributes<HTMLFieldSetElement>>;
|
|
1222
1226
|
export declare const Switch: React$1.ForwardRefExoticComponent<SwitchProps & React$1.RefAttributes<HTMLButtonElement>>;
|
package/dist/index.d.ts
CHANGED
|
@@ -417,9 +417,12 @@ export type SelectOption = string | {
|
|
|
417
417
|
label: string;
|
|
418
418
|
disabled?: boolean | undefined;
|
|
419
419
|
};
|
|
420
|
+
/** 5.3: opens AURA's own list (light and dark alike) over a real `<select>`, which keeps `name`, `required`, the ref,
|
|
421
|
+
* `onChange`, react-hook-form `register` and form posts. `multiple` or `size > 1` keeps the native list box. */
|
|
420
422
|
export interface SelectProps extends FieldProps, Omit<React$1.SelectHTMLAttributes<HTMLSelectElement>, "required"> {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
+
/** The choices; or pass `<option>` / `<optgroup>` children instead (5.3: optional). */
|
|
424
|
+
options?: SelectOption[] | undefined;
|
|
425
|
+
/** Shown in fg-tertiary on the closed field until something is chosen; not listed as a choice. */
|
|
423
426
|
placeholder?: string | undefined;
|
|
424
427
|
icon?: IconInput | undefined;
|
|
425
428
|
}
|
|
@@ -1217,6 +1220,7 @@ export interface FieldComponentProps extends Omit<FieldPropsPublic, "label"> {
|
|
|
1217
1220
|
export declare const Field: React$1.ForwardRefExoticComponent<FieldComponentProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
1218
1221
|
export declare const TextField: React$1.ForwardRefExoticComponent<TextFieldProps & React$1.RefAttributes<HTMLInputElement>>;
|
|
1219
1222
|
export declare const Textarea: React$1.ForwardRefExoticComponent<TextareaProps & React$1.RefAttributes<HTMLTextAreaElement>>;
|
|
1223
|
+
/** A select field: a button that opens an AURA list (5.3), over a real <select> that keeps forms, refs and events. */
|
|
1220
1224
|
export declare const Select: React$1.ForwardRefExoticComponent<SelectProps & React$1.RefAttributes<HTMLSelectElement>>;
|
|
1221
1225
|
export declare const RadioGroup: React$1.ForwardRefExoticComponent<RadioGroupProps & React$1.RefAttributes<HTMLFieldSetElement>>;
|
|
1222
1226
|
export declare const Switch: React$1.ForwardRefExoticComponent<SwitchProps & React$1.RefAttributes<HTMLButtonElement>>;
|
package/dist/styles.css
CHANGED
|
@@ -424,7 +424,16 @@ a.aura-icon-btn { text-decoration: none; }
|
|
|
424
424
|
.aura-select .aura-input__control { padding-right: calc(var(--aura-space-3) + var(--aura-icon-sm) + var(--aura-space-2)); cursor: pointer; }
|
|
425
425
|
.aura-select__chevron { position: absolute; right: var(--aura-space-3); color: var(--aura-fg-secondary); pointer-events: none; }
|
|
426
426
|
.aura-select select:invalid, .aura-select select:has(option[value=""]:checked) { color: var(--aura-fg-tertiary); }
|
|
427
|
+
/* 5.3: the field is a button over an invisible <select> (forms, refs, events); the list is AURA's, like Combobox. */
|
|
427
428
|
.aura-select option { color: var(--aura-fg-primary); background: var(--aura-bg-input); }
|
|
429
|
+
.aura-select--custom { position: relative; }
|
|
430
|
+
.aura-select__native { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; padding: 0; border: 0; opacity: 0; pointer-events: none; appearance: none; }
|
|
431
|
+
.aura-select__trigger { display: flex; align-items: center; text-align: start; background: none; border: 0; color: inherit; font: inherit; width: 100%; min-width: 0; }
|
|
432
|
+
.aura-select__trigger:disabled { cursor: not-allowed; }
|
|
433
|
+
.aura-select__value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
434
|
+
.aura-select__value.is-placeholder { color: var(--aura-fg-tertiary); }
|
|
435
|
+
.aura-select__group { padding: var(--aura-space-2) var(--aura-space-2) var(--aura-space-1); font-family: var(--font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--aura-fg-tertiary); }
|
|
436
|
+
.aura-select.is-open .aura-select__chevron { transform: rotate(180deg); }
|
|
428
437
|
|
|
429
438
|
.aura-radio-group > legend { margin-bottom: 6px; }
|
|
430
439
|
.aura-radio-group__list { display: flex; flex-direction: column; gap: var(--aura-space-3); }
|
package/dist/styles.layer.css
CHANGED
|
@@ -426,7 +426,16 @@ a.aura-icon-btn { text-decoration: none; }
|
|
|
426
426
|
.aura-select .aura-input__control { padding-right: calc(var(--aura-space-3) + var(--aura-icon-sm) + var(--aura-space-2)); cursor: pointer; }
|
|
427
427
|
.aura-select__chevron { position: absolute; right: var(--aura-space-3); color: var(--aura-fg-secondary); pointer-events: none; }
|
|
428
428
|
.aura-select select:invalid, .aura-select select:has(option[value=""]:checked) { color: var(--aura-fg-tertiary); }
|
|
429
|
+
/* 5.3: the field is a button over an invisible <select> (forms, refs, events); the list is AURA's, like Combobox. */
|
|
429
430
|
.aura-select option { color: var(--aura-fg-primary); background: var(--aura-bg-input); }
|
|
431
|
+
.aura-select--custom { position: relative; }
|
|
432
|
+
.aura-select__native { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; padding: 0; border: 0; opacity: 0; pointer-events: none; appearance: none; }
|
|
433
|
+
.aura-select__trigger { display: flex; align-items: center; text-align: start; background: none; border: 0; color: inherit; font: inherit; width: 100%; min-width: 0; }
|
|
434
|
+
.aura-select__trigger:disabled { cursor: not-allowed; }
|
|
435
|
+
.aura-select__value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
436
|
+
.aura-select__value.is-placeholder { color: var(--aura-fg-tertiary); }
|
|
437
|
+
.aura-select__group { padding: var(--aura-space-2) var(--aura-space-2) var(--aura-space-1); font-family: var(--font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--aura-fg-tertiary); }
|
|
438
|
+
.aura-select.is-open .aura-select__chevron { transform: rotate(180deg); }
|
|
430
439
|
|
|
431
440
|
.aura-radio-group > legend { margin-bottom: 6px; }
|
|
432
441
|
.aura-radio-group__list { display: flex; flex-direction: column; gap: var(--aura-space-3); }
|
package/package.json
CHANGED