@jirawatpyk/aura-react 5.2.0 → 5.4.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 +20 -4
- package/dist/aura.bundle.js +503 -39
- package/dist/cjs/index.cjs +503 -39
- package/dist/esm/Combobox.js +3 -2
- package/dist/esm/Select.js +479 -15
- package/dist/index.d.cts +29 -12
- package/dist/index.d.ts +29 -12
- package/dist/styles.css +9 -0
- package/dist/styles.layer.css +9 -0
- package/package.json +14 -14
- package/scripts/aura-theme.mjs +20 -8
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, devWarnOnce, 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,267 @@ 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
|
+
React.useEffect(function() {
|
|
70
|
+
if (props.label || props["aria-label"] || props["aria-labelledby"] || props.title) return;
|
|
71
|
+
if (document.querySelector('label[for="' + id.replace(/["\\]/g, "\\$&") + '"]')) return;
|
|
72
|
+
devWarnOnce(
|
|
73
|
+
"select-name",
|
|
74
|
+
"Select needs a label, aria-label or aria-labelledby: screen readers announce it with no name."
|
|
75
|
+
);
|
|
76
|
+
}, []);
|
|
77
|
+
const selRef = React.useRef(null), trigRef = React.useRef(null), listRef = React.useRef(null);
|
|
78
|
+
const shown = React.useState(function() {
|
|
79
|
+
return initialLabel(props);
|
|
80
|
+
});
|
|
81
|
+
const openState = React.useState(false), open = openState[0];
|
|
82
|
+
const activeState = React.useState(-1), active = activeState[0];
|
|
83
|
+
const items = React.useState([]);
|
|
84
|
+
const pos = React.useState(null);
|
|
85
|
+
const typed = React.useRef({ text: "", at: 0 });
|
|
86
|
+
const sync = React.useCallback(function() {
|
|
87
|
+
const el = selRef.current;
|
|
88
|
+
if (!el) return;
|
|
89
|
+
const o = el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null;
|
|
90
|
+
const next = o ? { label: o.textContent || "", empty: o.value === "" } : { label: "", empty: true };
|
|
91
|
+
shown[1](function(cur) {
|
|
92
|
+
return cur.label === next.label && cur.empty === next.empty ? cur : next;
|
|
93
|
+
});
|
|
94
|
+
}, []);
|
|
95
|
+
const hook = React.useCallback(
|
|
96
|
+
function(el) {
|
|
97
|
+
selRef.current = el;
|
|
98
|
+
if (!el || native || el.__auraHooked) return;
|
|
99
|
+
el.__auraHooked = true;
|
|
100
|
+
const proto = HTMLSelectElement.prototype;
|
|
101
|
+
const pv = Object.getOwnPropertyDescriptor(proto, "value"), pi = Object.getOwnPropertyDescriptor(proto, "selectedIndex");
|
|
102
|
+
if (pv && pv.set && pv.get)
|
|
103
|
+
Object.defineProperty(el, "value", {
|
|
104
|
+
configurable: true,
|
|
105
|
+
get: function() {
|
|
106
|
+
return pv.get.call(el);
|
|
107
|
+
},
|
|
108
|
+
set: function(v) {
|
|
109
|
+
pv.set.call(el, v);
|
|
110
|
+
sync();
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
if (pi && pi.set && pi.get)
|
|
114
|
+
Object.defineProperty(el, "selectedIndex", {
|
|
115
|
+
configurable: true,
|
|
116
|
+
get: function() {
|
|
117
|
+
return pi.get.call(el);
|
|
118
|
+
},
|
|
119
|
+
set: function(v) {
|
|
120
|
+
pi.set.call(el, v);
|
|
121
|
+
sync();
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
el.focus = function(o) {
|
|
125
|
+
if (trigRef.current) trigRef.current.focus(o);
|
|
126
|
+
else proto.focus.call(el, o);
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
[native, sync]
|
|
130
|
+
);
|
|
131
|
+
const selMerged = useMergedRef(ref, hook);
|
|
132
|
+
React.useEffect(
|
|
133
|
+
function() {
|
|
134
|
+
const f = selRef.current && selRef.current.form;
|
|
135
|
+
if (!f || native) return;
|
|
136
|
+
function onReset() {
|
|
137
|
+
setTimeout(sync, 0);
|
|
138
|
+
}
|
|
139
|
+
f.addEventListener("reset", onReset);
|
|
140
|
+
return function() {
|
|
141
|
+
f.removeEventListener("reset", onReset);
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
[native, sync]
|
|
145
|
+
);
|
|
146
|
+
const focusedOnce = React.useRef(false);
|
|
147
|
+
React.useEffect(
|
|
148
|
+
function() {
|
|
149
|
+
if (!live || focusedOnce.current || !trigRef.current) return;
|
|
150
|
+
focusedOnce.current = true;
|
|
151
|
+
if (props.autoFocus || selRef.current && document.activeElement === selRef.current) trigRef.current.focus();
|
|
152
|
+
},
|
|
153
|
+
[live]
|
|
154
|
+
);
|
|
155
|
+
useIsoLayoutEffect(function() {
|
|
156
|
+
if (!live) return;
|
|
157
|
+
sync();
|
|
158
|
+
if (!open) return;
|
|
159
|
+
const now = readItems(selRef.current), was = items[0];
|
|
160
|
+
const same = now.length === was.length && now.every(function(it, i) {
|
|
161
|
+
const w = was[i];
|
|
162
|
+
return w.value === it.value && w.label === it.label && w.disabled === it.disabled && w.group === it.group;
|
|
163
|
+
});
|
|
164
|
+
if (same) return;
|
|
165
|
+
items[1](now);
|
|
166
|
+
if (!choices(now).length) {
|
|
167
|
+
close(false);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const hv = was[active] ? was[active].value : null;
|
|
171
|
+
const again = now.filter(function(it) {
|
|
172
|
+
return !it.disabled && it.value === hv;
|
|
173
|
+
})[0];
|
|
174
|
+
const cur = selRef.current ? selRef.current.selectedIndex : -1;
|
|
175
|
+
activeState[1](again ? again.index : now[cur] && !now[cur].disabled ? cur : choices(now)[0].index);
|
|
176
|
+
});
|
|
177
|
+
function choices(list) {
|
|
178
|
+
return list.filter(function(i) {
|
|
179
|
+
return !i.disabled;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function choose(it) {
|
|
183
|
+
const el = selRef.current;
|
|
184
|
+
if (!el || it.disabled) return;
|
|
185
|
+
if (el.value !== it.value) {
|
|
186
|
+
el.value = it.value;
|
|
187
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
188
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
189
|
+
}
|
|
190
|
+
sync();
|
|
191
|
+
close(true);
|
|
192
|
+
}
|
|
193
|
+
function openList(at) {
|
|
194
|
+
if (props.disabled || trigRef.current && trigRef.current.disabled) return;
|
|
195
|
+
const list = readItems(selRef.current);
|
|
196
|
+
const ok = choices(list);
|
|
197
|
+
if (!ok.length) return;
|
|
198
|
+
items[1](list);
|
|
199
|
+
const cur = selRef.current ? selRef.current.selectedIndex : -1;
|
|
200
|
+
let a = at === "first" ? ok[0].index : at === "last" ? ok[ok.length - 1].index : cur;
|
|
201
|
+
if (typeof at === "number") a = at;
|
|
202
|
+
if (a < 0 || !list[a] || list[a].disabled) a = ok[0].index;
|
|
203
|
+
activeState[1](a);
|
|
204
|
+
openState[1](true);
|
|
205
|
+
}
|
|
206
|
+
function close(focus) {
|
|
207
|
+
openState[1](false);
|
|
208
|
+
pos[1](null);
|
|
209
|
+
if (focus && trigRef.current) trigRef.current.focus();
|
|
210
|
+
}
|
|
211
|
+
function step(from, dir, list) {
|
|
212
|
+
for (let i = from + dir; i >= 0 && i < list.length; i += dir) if (!list[i].disabled) return i;
|
|
213
|
+
return from;
|
|
214
|
+
}
|
|
215
|
+
function typing() {
|
|
216
|
+
return !!typed.current.text && Date.now() - typed.current.at < 500;
|
|
217
|
+
}
|
|
218
|
+
function typeahead(ch, list, from) {
|
|
219
|
+
const now = Date.now(), t = typed.current;
|
|
220
|
+
t.text = now - t.at < 500 ? t.text + ch : ch;
|
|
221
|
+
t.at = now;
|
|
222
|
+
const q = t.text.toLocaleLowerCase();
|
|
223
|
+
for (let k = 1; k <= list.length; k++) {
|
|
224
|
+
const i = (from + (t.text.length > 1 ? 0 : 1) + k - 1 + list.length) % list.length;
|
|
225
|
+
if (!list[i].disabled && list[i].label.toLocaleLowerCase().indexOf(q) === 0) return i;
|
|
226
|
+
}
|
|
227
|
+
t.text = "";
|
|
228
|
+
return -1;
|
|
229
|
+
}
|
|
230
|
+
function onKeyDown(e) {
|
|
231
|
+
const k = e.key;
|
|
232
|
+
const printable = k.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey;
|
|
233
|
+
const search = printable && (k !== " " || typing());
|
|
234
|
+
if (!open) {
|
|
235
|
+
if (search) {
|
|
236
|
+
const list2 = readItems(selRef.current);
|
|
237
|
+
const i = typeahead(k, list2, selRef.current ? selRef.current.selectedIndex : -1);
|
|
238
|
+
e.preventDefault();
|
|
239
|
+
if (i >= 0) openList(i);
|
|
240
|
+
} else if (k === "ArrowDown" || k === "ArrowUp" || k === "Enter" || k === " ") {
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
openList(k === "ArrowUp" && !(selRef.current && selRef.current.value) ? "last" : void 0);
|
|
243
|
+
}
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const list = items[0];
|
|
247
|
+
if (search) {
|
|
248
|
+
e.preventDefault();
|
|
249
|
+
const i = typeahead(k, list, active);
|
|
250
|
+
if (i >= 0) activeState[1](i);
|
|
251
|
+
} else if (k === "ArrowDown") {
|
|
252
|
+
e.preventDefault();
|
|
253
|
+
activeState[1](step(active, 1, list));
|
|
254
|
+
} else if (k === "ArrowUp") {
|
|
255
|
+
e.preventDefault();
|
|
256
|
+
activeState[1](step(active, -1, list));
|
|
257
|
+
} else if (k === "Home" || k === "PageUp") {
|
|
258
|
+
e.preventDefault();
|
|
259
|
+
activeState[1](k === "Home" ? step(-1, 1, list) : Math.max(step(-1, 1, list), step(active - 9, 1, list)));
|
|
260
|
+
} else if (k === "End" || k === "PageDown") {
|
|
261
|
+
e.preventDefault();
|
|
262
|
+
activeState[1](
|
|
263
|
+
k === "End" ? step(list.length, -1, list) : Math.min(step(list.length, -1, list), step(active + 9, -1, list))
|
|
264
|
+
);
|
|
265
|
+
} else if (k === "Enter" || k === " ") {
|
|
266
|
+
e.preventDefault();
|
|
267
|
+
if (list[active]) choose(list[active]);
|
|
268
|
+
} else if (k === "Escape") {
|
|
269
|
+
e.preventDefault();
|
|
270
|
+
e.stopPropagation();
|
|
271
|
+
close(true);
|
|
272
|
+
} else if (k === "Tab") {
|
|
273
|
+
close(false);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
useIsoLayoutEffect(
|
|
277
|
+
function() {
|
|
278
|
+
if (!open) return;
|
|
279
|
+
function place() {
|
|
280
|
+
const t = trigRef.current && trigRef.current.parentElement;
|
|
281
|
+
if (!t) return;
|
|
282
|
+
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;
|
|
283
|
+
pos[1]({
|
|
284
|
+
left: r.left,
|
|
285
|
+
width: r.width,
|
|
286
|
+
top: up ? void 0 : r.bottom + 4,
|
|
287
|
+
bottom: up ? window.innerHeight - r.top + 4 : void 0,
|
|
288
|
+
maxHeight: Math.min(320, (up ? above : below) - 4)
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
place();
|
|
292
|
+
function outside(e) {
|
|
293
|
+
const n = e.target;
|
|
294
|
+
if (listRef.current && listRef.current.contains(n)) return;
|
|
295
|
+
if (trigRef.current && trigRef.current.contains(n)) return;
|
|
296
|
+
close(false);
|
|
297
|
+
}
|
|
298
|
+
function onScroll(e) {
|
|
299
|
+
if (!listRef.current || !listRef.current.contains(e.target)) place();
|
|
300
|
+
}
|
|
301
|
+
document.addEventListener("pointerdown", outside, true);
|
|
302
|
+
window.addEventListener("scroll", onScroll, true);
|
|
303
|
+
window.addEventListener("resize", place);
|
|
304
|
+
return function() {
|
|
305
|
+
document.removeEventListener("pointerdown", outside, true);
|
|
306
|
+
window.removeEventListener("scroll", onScroll, true);
|
|
307
|
+
window.removeEventListener("resize", place);
|
|
308
|
+
};
|
|
309
|
+
},
|
|
310
|
+
[open]
|
|
311
|
+
);
|
|
312
|
+
React.useEffect(
|
|
313
|
+
function() {
|
|
314
|
+
if (!open || !listRef.current || active < 0) return;
|
|
315
|
+
const el = listRef.current.querySelector('[data-idx="' + active + '"]');
|
|
316
|
+
if (el && el.scrollIntoView) el.scrollIntoView({ block: "nearest" });
|
|
317
|
+
},
|
|
318
|
+
[open, active, pos[0] != null]
|
|
319
|
+
);
|
|
320
|
+
const described = [describedBy(id, props), rest["aria-describedby"]].filter(Boolean).join(" ") || void 0;
|
|
321
|
+
const field = (child) => /* @__PURE__ */ React.createElement(
|
|
20
322
|
Field,
|
|
21
323
|
{
|
|
22
324
|
id,
|
|
325
|
+
labelId: native ? void 0 : id + "-label",
|
|
23
326
|
label: props.label,
|
|
24
327
|
hint: props.hint,
|
|
25
328
|
error: props.error,
|
|
@@ -28,19 +331,180 @@ const Select = React.forwardRef(function Select2(props, ref) {
|
|
|
28
331
|
disabled: props.disabled,
|
|
29
332
|
className: props.className
|
|
30
333
|
},
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
334
|
+
child
|
|
335
|
+
);
|
|
336
|
+
if (!live)
|
|
337
|
+
return field(
|
|
338
|
+
/* @__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(
|
|
339
|
+
"select",
|
|
340
|
+
Object.assign(extra, rest, {
|
|
341
|
+
ref: native ? ref : selMerged,
|
|
342
|
+
id,
|
|
343
|
+
className: "aura-input__control",
|
|
344
|
+
required: props.required,
|
|
345
|
+
"aria-invalid": props.error ? true : void 0,
|
|
346
|
+
"aria-describedby": described
|
|
347
|
+
}),
|
|
348
|
+
opts,
|
|
349
|
+
props.children
|
|
350
|
+
), /* @__PURE__ */ React.createElement(Icon, { name: "chevron-down", className: "aura-select__chevron" }))
|
|
351
|
+
);
|
|
352
|
+
const listId = id + "-list", labelId = id + "-label";
|
|
353
|
+
const optId = function(i) {
|
|
354
|
+
return id + "-opt-" + i;
|
|
355
|
+
};
|
|
356
|
+
const labelledBy = rest["aria-labelledby"] || (props.label ? labelId : void 0);
|
|
357
|
+
const selIndex = selRef.current ? selRef.current.selectedIndex : -1;
|
|
358
|
+
const option = function(it) {
|
|
359
|
+
const sel = selIndex === it.index;
|
|
360
|
+
return /* @__PURE__ */ React.createElement(
|
|
361
|
+
"div",
|
|
362
|
+
{
|
|
363
|
+
key: it.index,
|
|
364
|
+
id: optId(it.index),
|
|
365
|
+
role: "option",
|
|
366
|
+
"data-idx": it.index,
|
|
367
|
+
"aria-selected": sel,
|
|
368
|
+
"aria-disabled": it.disabled || void 0,
|
|
369
|
+
className: cx(
|
|
370
|
+
"aura-combo__option",
|
|
371
|
+
it.index === active && "is-active",
|
|
372
|
+
sel && "is-selected",
|
|
373
|
+
it.disabled && "is-disabled"
|
|
374
|
+
),
|
|
375
|
+
onPointerDown: function(e) {
|
|
376
|
+
e.preventDefault();
|
|
377
|
+
},
|
|
378
|
+
onPointerMove: function() {
|
|
379
|
+
if (!it.disabled && active !== it.index) activeState[1](it.index);
|
|
380
|
+
},
|
|
381
|
+
onClick: function() {
|
|
382
|
+
choose(it);
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
/* @__PURE__ */ React.createElement("span", { className: "aura-combo__text" }, /* @__PURE__ */ React.createElement("span", { className: "aura-combo__label" }, it.label)),
|
|
386
|
+
sel ? /* @__PURE__ */ React.createElement(Icon, { name: "check", className: "aura-combo__check" }) : null
|
|
387
|
+
);
|
|
388
|
+
};
|
|
389
|
+
const runs = [];
|
|
390
|
+
items[0].forEach(function(it) {
|
|
391
|
+
if (it.value === "" && it.disabled) return;
|
|
392
|
+
const last = runs[runs.length - 1];
|
|
393
|
+
if (last && last.group === it.group) last.items.push(it);
|
|
394
|
+
else runs.push({ group: it.group, items: [it] });
|
|
395
|
+
});
|
|
396
|
+
const popup = open ? createPortal(
|
|
397
|
+
/* @__PURE__ */ React.createElement(
|
|
398
|
+
"div",
|
|
399
|
+
{
|
|
400
|
+
ref: listRef,
|
|
401
|
+
"data-density": density,
|
|
402
|
+
className: "aura-combo__popover aura-select__popover",
|
|
403
|
+
style: pos[0] ? {
|
|
404
|
+
left: pos[0].left,
|
|
405
|
+
minWidth: pos[0].width,
|
|
406
|
+
top: pos[0].top,
|
|
407
|
+
bottom: pos[0].bottom,
|
|
408
|
+
maxHeight: pos[0].maxHeight
|
|
409
|
+
} : { left: -9999, top: -9999 }
|
|
410
|
+
},
|
|
411
|
+
/* @__PURE__ */ React.createElement(
|
|
412
|
+
"div",
|
|
413
|
+
{
|
|
414
|
+
id: listId,
|
|
415
|
+
role: "listbox",
|
|
416
|
+
"aria-labelledby": labelledBy,
|
|
417
|
+
"aria-label": labelledBy ? void 0 : rest["aria-label"],
|
|
418
|
+
className: "aura-combo__list"
|
|
419
|
+
},
|
|
420
|
+
runs.map(function(r, n) {
|
|
421
|
+
if (r.group === null) return /* @__PURE__ */ React.createElement(React.Fragment, { key: "r" + n }, r.items.map(option));
|
|
422
|
+
const gid = id + "-group-" + n;
|
|
423
|
+
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));
|
|
424
|
+
})
|
|
425
|
+
)
|
|
426
|
+
),
|
|
427
|
+
document.body
|
|
428
|
+
) : null;
|
|
429
|
+
const userFocus = rest.onFocus, userBlur = rest.onBlur, userKey = rest.onKeyDown, userClick = rest.onClick;
|
|
430
|
+
return field(
|
|
431
|
+
/* @__PURE__ */ React.createElement(
|
|
432
|
+
"div",
|
|
433
|
+
{
|
|
434
|
+
className: cx("aura-input aura-select aura-select--custom", props.icon && "has-icon", open && "is-open"),
|
|
435
|
+
"data-invalid": props.error ? "" : void 0
|
|
436
|
+
},
|
|
437
|
+
props.icon ? /* @__PURE__ */ React.createElement(Icon, { name: props.icon, className: "aura-input__icon" }) : null,
|
|
438
|
+
h(
|
|
439
|
+
"select",
|
|
440
|
+
Object.assign(extra, pick(rest, FORM_KEYS), {
|
|
441
|
+
ref: selMerged,
|
|
442
|
+
id: id + "-select",
|
|
443
|
+
className: "aura-select__native",
|
|
444
|
+
tabIndex: -1,
|
|
445
|
+
"aria-hidden": true,
|
|
446
|
+
required: props.required,
|
|
447
|
+
onChange: function(e) {
|
|
448
|
+
sync();
|
|
449
|
+
if (props.onChange) props.onChange(e);
|
|
450
|
+
},
|
|
451
|
+
/* A browser focusing the <select> (the required bubble, a label click) hands focus to the button. */
|
|
452
|
+
onFocus: function() {
|
|
453
|
+
if (trigRef.current) trigRef.current.focus();
|
|
454
|
+
},
|
|
455
|
+
/* Only the blur the button reports (below): the <select>'s own, while it hands focus over, is not one. */
|
|
456
|
+
onBlur: function(e) {
|
|
457
|
+
if (!e.nativeEvent.isTrusted && userBlur) userBlur(e);
|
|
458
|
+
}
|
|
459
|
+
}),
|
|
460
|
+
opts,
|
|
461
|
+
props.children
|
|
462
|
+
),
|
|
463
|
+
/* @__PURE__ */ React.createElement(
|
|
464
|
+
"button",
|
|
465
|
+
{
|
|
466
|
+
...omit(rest, HANDLED_KEYS),
|
|
467
|
+
ref: trigRef,
|
|
468
|
+
type: "button",
|
|
469
|
+
role: "combobox",
|
|
470
|
+
form: id + "-no-form",
|
|
471
|
+
className: "aura-input__control aura-select__trigger",
|
|
472
|
+
disabled: props.disabled,
|
|
473
|
+
tabIndex: rest.tabIndex,
|
|
474
|
+
"aria-haspopup": "listbox",
|
|
475
|
+
"aria-expanded": open,
|
|
476
|
+
"aria-controls": open ? listId : void 0,
|
|
477
|
+
"aria-activedescendant": open && active >= 0 ? optId(active) : void 0,
|
|
478
|
+
id,
|
|
479
|
+
"aria-label": rest["aria-label"],
|
|
480
|
+
"aria-labelledby": rest["aria-labelledby"],
|
|
481
|
+
"aria-required": props.required || void 0,
|
|
482
|
+
"aria-invalid": props.error ? true : void 0,
|
|
483
|
+
"aria-describedby": described,
|
|
484
|
+
onFocus: function(e) {
|
|
485
|
+
if (userFocus) userFocus(e);
|
|
486
|
+
},
|
|
487
|
+
onClick: function(e) {
|
|
488
|
+
if (userClick) userClick(e);
|
|
489
|
+
if (e.defaultPrevented) return;
|
|
490
|
+
if (open) close(true);
|
|
491
|
+
else openList();
|
|
492
|
+
},
|
|
493
|
+
onKeyDown: function(e) {
|
|
494
|
+
if (userKey) userKey(e);
|
|
495
|
+
if (!e.defaultPrevented) onKeyDown(e);
|
|
496
|
+
},
|
|
497
|
+
onBlur: function(e) {
|
|
498
|
+
if (listRef.current && e.relatedTarget && listRef.current.contains(e.relatedTarget)) return;
|
|
499
|
+
if (open) close(false);
|
|
500
|
+
if (selRef.current) selRef.current.dispatchEvent(new FocusEvent("focusout", { bubbles: true }));
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
/* @__PURE__ */ React.createElement("span", { id: id + "-value", className: cx("aura-select__value", shown[0].empty && "is-placeholder") }, shown[0].label || " ")
|
|
504
|
+
),
|
|
505
|
+
/* @__PURE__ */ React.createElement(Icon, { name: "chevron-down", className: "aura-select__chevron" }),
|
|
506
|
+
popup
|
|
507
|
+
)
|
|
44
508
|
);
|
|
45
509
|
});
|
|
46
510
|
export {
|
package/dist/index.d.cts
CHANGED
|
@@ -46,6 +46,11 @@ export interface DataTableState {
|
|
|
46
46
|
sort: DataTableSort | null;
|
|
47
47
|
page: number;
|
|
48
48
|
}
|
|
49
|
+
/** A callback that gets one row. Written with your own row type (`(r: Order) => …`) it still fits (5.4):
|
|
50
|
+
* the row is checked the way method parameters are, so no cast from `Record<string, any>` is needed. */
|
|
51
|
+
export type RowCallback<R> = {
|
|
52
|
+
bivarianceHack(row: Record<string, any>): R;
|
|
53
|
+
}["bivarianceHack"];
|
|
49
54
|
export interface DataTableColumn {
|
|
50
55
|
/** Key into each row object. */
|
|
51
56
|
key: string;
|
|
@@ -62,9 +67,9 @@ export interface DataTableColumn {
|
|
|
62
67
|
/** Header becomes a button cycling asc → desc → unsorted. */
|
|
63
68
|
sortable?: boolean | undefined;
|
|
64
69
|
/** Value to sort by, when not the cell value (dates, amounts). */
|
|
65
|
-
sortValue?:
|
|
70
|
+
sortValue?: RowCallback<string | number | null> | undefined;
|
|
66
71
|
/** Custom cell content. */
|
|
67
|
-
render?:
|
|
72
|
+
render?: RowCallback<React$1.ReactNode> | undefined;
|
|
68
73
|
/** Set false to keep a sized column fixed when the table is `resizable`. */
|
|
69
74
|
resizable?: boolean | undefined;
|
|
70
75
|
/** Resize limits in px. Defaults 64 / 480. For the flexible column, minWidth defaults to 160. */
|
|
@@ -98,7 +103,9 @@ export interface DataTableEmpty {
|
|
|
98
103
|
export interface DataTableProps {
|
|
99
104
|
/** Defaults to ID · NAME · STATUS · OWNER (96 / 160 / 112 / auto px). */
|
|
100
105
|
columns?: DataTableColumn[] | undefined;
|
|
101
|
-
|
|
106
|
+
/** Row objects. Any typed interface works (5.4: before, `interface Order {…}` rows needed a cast); cells show
|
|
107
|
+
* the value at each column's key unless the column has `render`. */
|
|
108
|
+
rows: ReadonlyArray<Record<string, any>>;
|
|
102
109
|
/** Column key giving each row a unique React key. Default: the first column's key. */
|
|
103
110
|
rowKey?: string | undefined;
|
|
104
111
|
/** Accessible name for the table. */
|
|
@@ -145,7 +152,7 @@ export interface DataTableProps {
|
|
|
145
152
|
totalRows?: number | undefined;
|
|
146
153
|
/** Makes each row a link: the first column's content renders as the provider's linkComponent (or `<a>`), and a click
|
|
147
154
|
* or Enter anywhere on the row follows it. Ctrl/⌘-click opens a new tab as usual. */
|
|
148
|
-
getRowHref?:
|
|
155
|
+
getRowHref?: RowCallback<string> | undefined;
|
|
149
156
|
/** Pager arrows become links to these URLs (search-param paging). Without onPageChange the link navigates. */
|
|
150
157
|
getPageHref?: ((page: number) => string) | undefined;
|
|
151
158
|
/** Router link for getRowHref / getPageHref. Default: AuraProvider's linkComponent, else `<a>`. */
|
|
@@ -153,7 +160,7 @@ export interface DataTableProps {
|
|
|
153
160
|
/** Skeleton row count when there is no pageSize. Default 5. */
|
|
154
161
|
skeletonRows?: number | undefined;
|
|
155
162
|
/** Called on row click or Enter. */
|
|
156
|
-
onRowActivate?:
|
|
163
|
+
onRowActivate?: RowCallback<void> | undefined;
|
|
157
164
|
/** Fixed height in px: sticky header, and only the rows in view are rendered. */
|
|
158
165
|
height?: number | undefined;
|
|
159
166
|
/** Column menu on every header, drag-to-reorder, and the show/hide columns button. */
|
|
@@ -321,11 +328,14 @@ export interface FormErrorItem {
|
|
|
321
328
|
message: React$1.ReactNode;
|
|
322
329
|
}
|
|
323
330
|
/** The list of problems at the top of a form after a failed submit; each links to its field (GOV.UK pattern). */
|
|
331
|
+
/** react-hook-form's `formState.errors` (FieldErrors) or any object shaped like it: a `message` at any depth. */
|
|
332
|
+
export type FormErrorTree = {
|
|
333
|
+
readonly [field: string]: object | undefined;
|
|
334
|
+
};
|
|
324
335
|
export interface FormErrorSummaryProps {
|
|
325
|
-
/** A list, or react-hook-form's `formState.errors` as is
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
} | undefined>;
|
|
336
|
+
/** A list, or react-hook-form's `formState.errors` as is: `{ name: { message } }`, nested for `address.street`
|
|
337
|
+
* and field arrays (`items.0.name`). Empty → renders nothing. */
|
|
338
|
+
errors: FormErrorItem[] | FormErrorTree;
|
|
329
339
|
/** Default "Fix N fields to continue" in the provider's language. */
|
|
330
340
|
title?: React$1.ReactNode | undefined;
|
|
331
341
|
/** Called with the field when a link is followed, e.g. react-hook-form's `setFocus`. Default: focus the element whose id (or name) is the field. */
|
|
@@ -417,9 +427,15 @@ export type SelectOption = string | {
|
|
|
417
427
|
label: string;
|
|
418
428
|
disabled?: boolean | undefined;
|
|
419
429
|
};
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
430
|
+
/** 5.3: opens AURA's own list (light and dark alike) over a real `<select>`, which keeps `name`, `required`, the ref,
|
|
431
|
+
* `onChange`, react-hook-form `register` and form posts. `multiple` or `size > 1` keeps the native list box. */
|
|
432
|
+
export interface SelectProps extends Omit<FieldProps, "label">, Omit<React$1.SelectHTMLAttributes<HTMLSelectElement>, "required"> {
|
|
433
|
+
/** Visible label; also the accessible name. Omit only with `aria-label` or `aria-labelledby` (a toolbar, a table
|
|
434
|
+
* cell); development builds warn when the field has no name at all. */
|
|
435
|
+
label?: string | undefined;
|
|
436
|
+
/** The choices; or pass `<option>` / `<optgroup>` children instead (5.3: optional). */
|
|
437
|
+
options?: SelectOption[] | undefined;
|
|
438
|
+
/** Shown in fg-tertiary on the closed field until something is chosen; not listed as a choice. */
|
|
423
439
|
placeholder?: string | undefined;
|
|
424
440
|
icon?: IconInput | undefined;
|
|
425
441
|
}
|
|
@@ -1217,6 +1233,7 @@ export interface FieldComponentProps extends Omit<FieldPropsPublic, "label"> {
|
|
|
1217
1233
|
export declare const Field: React$1.ForwardRefExoticComponent<FieldComponentProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
1218
1234
|
export declare const TextField: React$1.ForwardRefExoticComponent<TextFieldProps & React$1.RefAttributes<HTMLInputElement>>;
|
|
1219
1235
|
export declare const Textarea: React$1.ForwardRefExoticComponent<TextareaProps & React$1.RefAttributes<HTMLTextAreaElement>>;
|
|
1236
|
+
/** A select field: a button that opens an AURA list (5.3), over a real <select> that keeps forms, refs and events. */
|
|
1220
1237
|
export declare const Select: React$1.ForwardRefExoticComponent<SelectProps & React$1.RefAttributes<HTMLSelectElement>>;
|
|
1221
1238
|
export declare const RadioGroup: React$1.ForwardRefExoticComponent<RadioGroupProps & React$1.RefAttributes<HTMLFieldSetElement>>;
|
|
1222
1239
|
export declare const Switch: React$1.ForwardRefExoticComponent<SwitchProps & React$1.RefAttributes<HTMLButtonElement>>;
|