@jirawatpyk/aura-react 5.5.0 → 5.7.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.
@@ -52,7 +52,27 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
52
52
  );
53
53
  const sort = sortState[0], setSort = sortState[1];
54
54
  const selState = useMaybeControlled(props.selected, props.defaultSelected || [], props.onSelectionChange);
55
- const selected = selState[0], setSelected = selState[1];
55
+ const canSelect = function(r) {
56
+ return !!r && (!props.isRowSelectable || !!props.isRowSelectable(r));
57
+ };
58
+ const rejected = {};
59
+ if (props.isRowSelectable)
60
+ rows.forEach(function(r) {
61
+ if (!canSelect(r)) rejected[String(r[rowKey])] = true;
62
+ });
63
+ const selected = selState[0].filter(function(k) {
64
+ return !rejected[String(k)];
65
+ }), setSelected = selState[1];
66
+ const droppedSig = selState[0].length === selected.length ? "" : "#" + selState[0].filter(function(k) {
67
+ return rejected[String(k)];
68
+ }).join("\0") + "#" + selected.join("\0");
69
+ React.useEffect(
70
+ function() {
71
+ if (droppedSig !== "") setSelected(selected);
72
+ },
73
+ // eslint-disable-next-line react-hooks/exhaustive-deps
74
+ [droppedSig]
75
+ );
56
76
  const pageState = useMaybeControlled(
57
77
  props.page,
58
78
  props.defaultPage || 1,
@@ -463,7 +483,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
463
483
  el.focus();
464
484
  }
465
485
  }
466
- const pageKeys = pageRows.map(function(r) {
486
+ const pageKeys = pageRows.filter(canSelect).map(function(r) {
467
487
  return r[rowKey];
468
488
  });
469
489
  const selSet = {};
@@ -727,10 +747,12 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
727
747
  if (inner) inner.focus();
728
748
  else handled = false;
729
749
  } else if (k === " " || k === "Enter") {
730
- if (r === 0 && !col && selectable && nRows) toggleAll();
731
- else if (r === 0 && col && col.sortable && canSortAny) sortBy(col.key);
732
- else if (r > 0 && k === " " && selectable) toggle(row[rowKey], !selSet[row[rowKey]]);
733
- else if (r > 0 && k === "Enter" && rowHref && !target.querySelector("button, a[href]:not(.aura-table__row-link), input, select, textarea"))
750
+ if (r === 0 && !col && selectable && nRows) {
751
+ if (pageKeys.length) toggleAll();
752
+ } else if (r === 0 && col && col.sortable && canSortAny) sortBy(col.key);
753
+ else if (r > 0 && k === " " && selectable) {
754
+ if (canSelect(row)) toggle(row[rowKey], !selSet[row[rowKey]]);
755
+ } else if (r > 0 && k === "Enter" && rowHref && !target.querySelector("button, a[href]:not(.aura-table__row-link), input, select, textarea"))
734
756
  followRow(target.closest('[role="row"]'), e);
735
757
  else if (r > 0 && k === "Enter" && target.querySelector("button, a[href], input, select, textarea"))
736
758
  target.querySelector("button, a[href], input, select, textarea").focus();
@@ -759,7 +781,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
759
781
  activeState[1]({ r: 0, c: 0 });
760
782
  }
761
783
  },
762
- nRows ? /* @__PURE__ */ React.createElement(
784
+ pageKeys.length ? /* @__PURE__ */ React.createElement(
763
785
  Checkbox,
764
786
  {
765
787
  hideLabel: true,
@@ -770,7 +792,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
770
792
  onChange: toggleAll
771
793
  }
772
794
  ) : null,
773
- nRows ? /* @__PURE__ */ React.createElement("span", { className: "aura-table__sel-text" }, nSel ? t.selectedCount(nSel) : t.selectAll) : /* @__PURE__ */ React.createElement("span", { className: "aura-sr-only" }, t.selectRows)
795
+ pageKeys.length ? /* @__PURE__ */ React.createElement("span", { className: "aura-table__sel-text" }, nSel ? t.selectedCount(nSel) : t.selectAll) : /* @__PURE__ */ React.createElement("span", { className: "aura-sr-only" }, t.selectRows)
774
796
  )
775
797
  );
776
798
  vis.forEach(function(c, i) {
@@ -908,12 +930,12 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
908
930
  className: cx("aura-table__sel", nPinned && "is-pinned"),
909
931
  tabIndex: tabFor(ri, 0),
910
932
  "data-rc": ri + ":0",
911
- "aria-label": t.selectRow(k),
933
+ "aria-label": canSelect(r) ? t.selectRow(k) : props.rowSelectDisabledLabel ? props.rowSelectDisabledLabel(r) : void 0,
912
934
  onFocus: function(e) {
913
935
  if (e.target === e.currentTarget) activeState[1]({ r: ri, c: 0 });
914
936
  }
915
937
  },
916
- /* @__PURE__ */ React.createElement(
938
+ canSelect(r) ? /* @__PURE__ */ React.createElement(
917
939
  Checkbox,
918
940
  {
919
941
  hideLabel: true,
@@ -924,7 +946,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
924
946
  toggle(k, on);
925
947
  }
926
948
  }
927
- )
949
+ ) : null
928
950
  )
929
951
  );
930
952
  vis.forEach(function(c, j) {
@@ -1027,7 +1049,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
1027
1049
  key: k,
1028
1050
  role: "row",
1029
1051
  "aria-rowindex": first + i + 2,
1030
- "aria-selected": selectable ? isSel : void 0,
1052
+ "aria-selected": selectable && canSelect(r) ? isSel : void 0,
1031
1053
  className: cx(
1032
1054
  "aura-table__row",
1033
1055
  isSel && "is-selected",
@@ -12,6 +12,7 @@ const Dialog = React.forwardRef(function Dialog2(props, ref) {
12
12
  function close() {
13
13
  if (props.dismissible !== false && props.onClose) props.onClose();
14
14
  }
15
+ const scrimCloses = props.dismissible !== false && (props.dismissOnScrim !== void 0 ? props.dismissOnScrim : props.role !== "alertdialog");
15
16
  const modal = useModal(props.open, own, {
16
17
  autoFocus: props.autoFocus,
17
18
  onEscape: close,
@@ -20,7 +21,17 @@ const Dialog = React.forwardRef(function Dialog2(props, ref) {
20
21
  });
21
22
  if (!modal.ready) return null;
22
23
  return createPortal(
23
- /* @__PURE__ */ React.createElement("div", { className: "aura-dialog-layer", "data-density": density, onKeyDown: modal.onKeyDown }, /* @__PURE__ */ React.createElement("div", { className: "aura-scrim", onClick: close, "aria-hidden": true }), /* @__PURE__ */ React.createElement(
24
+ /* @__PURE__ */ React.createElement("div", { className: "aura-dialog-layer", "data-density": density, onKeyDown: modal.onKeyDown }, /* @__PURE__ */ React.createElement(
25
+ "div",
26
+ {
27
+ className: "aura-scrim",
28
+ onClick: scrimCloses ? close : void 0,
29
+ onMouseDown: scrimCloses ? void 0 : function(e) {
30
+ e.preventDefault();
31
+ },
32
+ "aria-hidden": true
33
+ }
34
+ ), /* @__PURE__ */ React.createElement(
24
35
  "div",
25
36
  {
26
37
  ref: merged,
@@ -54,8 +65,19 @@ const Drawer = React.forwardRef(function Drawer2(props, ref) {
54
65
  });
55
66
  if (!modal.ready) return null;
56
67
  const side = props.side === "left" ? "left" : "right";
68
+ const drawerScrimCloses = props.dismissible !== false && props.dismissOnScrim !== false;
57
69
  return createPortal(
58
- /* @__PURE__ */ React.createElement("div", { className: "aura-dialog-layer aura-drawer-layer", "data-density": density, onKeyDown: modal.onKeyDown }, /* @__PURE__ */ React.createElement("div", { className: "aura-scrim", onClick: close, "aria-hidden": true }), /* @__PURE__ */ React.createElement(
70
+ /* @__PURE__ */ React.createElement("div", { className: "aura-dialog-layer aura-drawer-layer", "data-density": density, onKeyDown: modal.onKeyDown }, /* @__PURE__ */ React.createElement(
71
+ "div",
72
+ {
73
+ className: "aura-scrim",
74
+ onClick: drawerScrimCloses ? close : void 0,
75
+ onMouseDown: !drawerScrimCloses ? function(e) {
76
+ e.preventDefault();
77
+ } : void 0,
78
+ "aria-hidden": true
79
+ }
80
+ ), /* @__PURE__ */ React.createElement(
59
81
  "div",
60
82
  {
61
83
  ref: merged,
@@ -36,6 +36,7 @@ const DropdownMenu = React.forwardRef(function DropdownMenu2(props, ref) {
36
36
  label: props.label,
37
37
  items: props.items,
38
38
  linkComponent: props.linkComponent,
39
+ header: props.header,
39
40
  onClose: function(restore) {
40
41
  setAnchor(null);
41
42
  if (restore && typeof anchor.focus === "function") anchor.focus();
package/dist/esm/Menu.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import * as React from "react";
3
3
  import { createPortal } from "react-dom";
4
- import { cx, useIsoLayoutEffect, useMounted, useMergedRef } from "./internal.js";
4
+ import { cx, uid, useIsoLayoutEffect, useMounted, useMergedRef } from "./internal.js";
5
5
  import { Icon } from "./Icon.js";
6
6
  import { useLinkComponent } from "./locale.js";
7
7
  const Menu = React.forwardRef(function Menu2(props, ref) {
@@ -10,6 +10,8 @@ const Menu = React.forwardRef(function Menu2(props, ref) {
10
10
  const pos = posState[0], setPos = posState[1];
11
11
  const items = props.items || [];
12
12
  const mounted = useMounted();
13
+ const headerId = uid();
14
+ const hasHeader = props.header != null && props.header !== false && props.header !== "";
13
15
  const Link = useLinkComponent(props.linkComponent);
14
16
  useIsoLayoutEffect(
15
17
  function() {
@@ -27,7 +29,20 @@ const Menu = React.forwardRef(function Menu2(props, ref) {
27
29
  const left = Math.max(8, Math.min(r.right - mw, window.innerWidth - mw - 8));
28
30
  setPos({ top, left, maxHeight });
29
31
  },
30
- [props.anchor, mounted]
32
+ [props.anchor, mounted, hasHeader]
33
+ );
34
+ const hadHeader = React.useRef(hasHeader);
35
+ React.useEffect(
36
+ function() {
37
+ if (hadHeader.current === hasHeader) return;
38
+ hadHeader.current = hasHeader;
39
+ const a = document.activeElement;
40
+ if (own.current && (!a || a === document.body)) {
41
+ const first = own.current.querySelector('[role^="menuitem"]:not([disabled])');
42
+ if (first) first.focus();
43
+ }
44
+ },
45
+ [hasHeader]
31
46
  );
32
47
  React.useEffect(
33
48
  function() {
@@ -53,22 +68,22 @@ const Menu = React.forwardRef(function Menu2(props, ref) {
53
68
  [mounted]
54
69
  );
55
70
  function onKeyDown(e) {
56
- const list = Array.prototype.slice.call(
71
+ const list2 = Array.prototype.slice.call(
57
72
  own.current.querySelectorAll('[role^="menuitem"]:not([disabled])')
58
73
  );
59
- const i = list.indexOf(document.activeElement);
74
+ const i = list2.indexOf(document.activeElement);
60
75
  if (e.key === "ArrowDown") {
61
76
  e.preventDefault();
62
- list[(i + 1) % list.length].focus();
77
+ list2[(i + 1) % list2.length].focus();
63
78
  } else if (e.key === "ArrowUp") {
64
79
  e.preventDefault();
65
- list[(i - 1 + list.length) % list.length].focus();
80
+ list2[(i - 1 + list2.length) % list2.length].focus();
66
81
  } else if (e.key === "Home") {
67
82
  e.preventDefault();
68
- list[0].focus();
83
+ list2[0].focus();
69
84
  } else if (e.key === "End") {
70
85
  e.preventDefault();
71
- list[list.length - 1].focus();
86
+ list2[list2.length - 1].focus();
72
87
  } else if (e.key === " " && document.activeElement && document.activeElement.tagName === "A") {
73
88
  e.preventDefault();
74
89
  document.activeElement.click();
@@ -80,9 +95,9 @@ const Menu = React.forwardRef(function Menu2(props, ref) {
80
95
  }
81
96
  e.stopPropagation();
82
97
  }
83
- function groupItems(list) {
98
+ function groupItems(list2) {
84
99
  const out = [];
85
- list.forEach(function(it, i) {
100
+ list2.forEach(function(it, i) {
86
101
  const g = it.type === "radio" && it.group ? it.group : null;
87
102
  const last = out[out.length - 1];
88
103
  if (last && last.group === g && g !== null) last.items.push({ it, i });
@@ -137,27 +152,28 @@ const Menu = React.forwardRef(function Menu2(props, ref) {
137
152
  body
138
153
  );
139
154
  }
140
- const el = /* @__PURE__ */ React.createElement(
155
+ const style = {
156
+ top: pos ? pos.top : -9999,
157
+ left: pos ? pos.left : -9999,
158
+ maxHeight: pos && pos.maxHeight ? pos.maxHeight : void 0
159
+ };
160
+ const list = groupItems(items).map(function(block, bi) {
161
+ const rendered = block.items.map(function(x) {
162
+ return renderItem(x.it, x.i);
163
+ });
164
+ return block.group ? /* @__PURE__ */ React.createElement("div", { key: "g" + bi, role: "group", "aria-label": block.group, className: "aura-menu__group" }, rendered) : /* @__PURE__ */ React.createElement(React.Fragment, { key: "f" + bi }, rendered);
165
+ });
166
+ const el = hasHeader ? /* @__PURE__ */ React.createElement("div", { ref: merged, className: "aura-menu has-header", onKeyDown, style }, /* @__PURE__ */ React.createElement(
141
167
  "div",
142
168
  {
143
- ref: merged,
144
- role: "menu",
145
- "aria-label": props.label,
146
- className: "aura-menu",
147
- onKeyDown,
148
- style: {
149
- top: pos ? pos.top : -9999,
150
- left: pos ? pos.left : -9999,
151
- maxHeight: pos && pos.maxHeight ? pos.maxHeight : void 0
169
+ className: "aura-menu__header",
170
+ id: headerId,
171
+ onMouseDown: function(e) {
172
+ e.preventDefault();
152
173
  }
153
174
  },
154
- groupItems(items).map(function(block, bi) {
155
- const rendered = block.items.map(function(x) {
156
- return renderItem(x.it, x.i);
157
- });
158
- return block.group ? /* @__PURE__ */ React.createElement("div", { key: "g" + bi, role: "group", "aria-label": block.group, className: "aura-menu__group" }, rendered) : /* @__PURE__ */ React.createElement(React.Fragment, { key: "f" + bi }, rendered);
159
- })
160
- );
175
+ props.header
176
+ ), /* @__PURE__ */ React.createElement("div", { role: "menu", "aria-label": props.label, "aria-describedby": headerId, className: "aura-menu__list" }, list)) : /* @__PURE__ */ React.createElement("div", { ref: merged, role: "menu", "aria-label": props.label, className: "aura-menu", onKeyDown, style }, list);
161
177
  return mounted ? createPortal(el, document.body) : null;
162
178
  });
163
179
  export {
@@ -3,8 +3,8 @@ import * as React from "react";
3
3
  import { ALERT_ICON } from "./Alert.js";
4
4
  import { Icon } from "./Icon.js";
5
5
  import { IconButton } from "./IconButton.js";
6
- import { cx, useMounted } from "./internal.js";
7
- import { useStrings } from "./locale.js";
6
+ import { cx, plainClick, useIsoLayoutEffect, useMounted } from "./internal.js";
7
+ import { useLinkComponent, useStrings } from "./locale.js";
8
8
  import { createPortal } from "react-dom";
9
9
  const MAX_VISIBLE = 3;
10
10
  const toastState = {
@@ -73,10 +73,35 @@ toast.dismiss = function(id) {
73
73
  promote();
74
74
  emitToasts();
75
75
  };
76
+ const hotkeyOrigin = /* @__PURE__ */ new WeakMap();
77
+ function outsideToaster(el) {
78
+ return el && el !== document.body && !el.closest(".aura-toaster") ? el : null;
79
+ }
76
80
  function ToastItem(props) {
77
81
  const str = useStrings();
82
+ const Link = useLinkComponent();
83
+ const node = React.useRef(null);
84
+ const origin = React.useRef(null);
85
+ useIsoLayoutEffect(function() {
86
+ origin.current = outsideToaster(document.activeElement);
87
+ const el = node.current;
88
+ return function() {
89
+ if (!el || !el.contains(document.activeElement)) return;
90
+ const back = [hotkeyOrigin.get(el), origin.current].filter(function(b) {
91
+ return !!b && b.isConnected;
92
+ })[0];
93
+ hotkeyOrigin.delete(el);
94
+ setTimeout(function() {
95
+ if (back && back.isConnected && (!document.activeElement || document.activeElement === document.body))
96
+ back.focus();
97
+ }, 0);
98
+ };
99
+ }, []);
78
100
  const t = props.toast, timer = React.useRef(null), left = React.useRef(t.duration || 5e3), since = React.useRef(0);
79
- const hover = React.useRef(false);
101
+ const hover = React.useRef(false), focused = React.useRef(false);
102
+ function syncFocus() {
103
+ if (focused.current && !(node.current && node.current.contains(document.activeElement))) focused.current = false;
104
+ }
80
105
  function start() {
81
106
  if (left.current === Infinity || timer.current) return;
82
107
  since.current = Date.now();
@@ -85,11 +110,10 @@ function ToastItem(props) {
85
110
  }, left.current);
86
111
  }
87
112
  function resume() {
88
- hover.current = false;
89
- start();
113
+ syncFocus();
114
+ if (!hover.current && !focused.current) start();
90
115
  }
91
116
  function pause() {
92
- hover.current = true;
93
117
  if (timer.current) {
94
118
  clearTimeout(timer.current);
95
119
  timer.current = null;
@@ -101,23 +125,56 @@ function ToastItem(props) {
101
125
  if (timer.current) clearTimeout(timer.current);
102
126
  timer.current = null;
103
127
  left.current = t.duration || 5e3;
104
- if (!hover.current) start();
128
+ syncFocus();
129
+ if (!hover.current && !focused.current) start();
105
130
  return function() {
106
131
  if (timer.current) clearTimeout(timer.current);
107
132
  };
108
133
  },
109
134
  [t.rev]
110
135
  );
136
+ const rich = t.description != null && typeof t.description !== "boolean" && t.description !== "";
137
+ const actions = (t.actions && t.actions.length ? t.actions : t.action ? [t.action] : []).slice(0, 2);
138
+ function run(a) {
139
+ if (a.onClick) a.onClick();
140
+ if (a.dismiss !== false) toast.dismiss(t.id);
141
+ }
111
142
  return /* @__PURE__ */ React.createElement(
112
143
  "div",
113
144
  {
114
- className: cx("aura-toast", "aura-toast--" + t.tone, t.loading && "is-loading"),
145
+ ref: node,
146
+ onKeyDown: function(e) {
147
+ if (e.key === "Escape") {
148
+ e.stopPropagation();
149
+ toast.dismiss(t.id);
150
+ }
151
+ },
152
+ className: cx(
153
+ "aura-toast",
154
+ "aura-toast--" + t.tone,
155
+ t.loading && "is-loading",
156
+ /* Two actions, or one beside a description, go on their own row under the text so it keeps its width. */
157
+ (actions.length > 1 || actions.length && rich) && "has-action-row"
158
+ ),
115
159
  role: t.tone === "danger" ? "alert" : "status",
116
160
  "aria-busy": t.loading || void 0,
117
- onMouseEnter: pause,
118
- onMouseLeave: resume,
119
- onFocus: pause,
120
- onBlur: resume
161
+ onMouseEnter: function() {
162
+ hover.current = true;
163
+ pause();
164
+ },
165
+ onMouseLeave: function() {
166
+ hover.current = false;
167
+ resume();
168
+ },
169
+ onFocus: function() {
170
+ focused.current = true;
171
+ pause();
172
+ },
173
+ onBlur: function(e) {
174
+ if (node.current && e.relatedTarget && node.current.contains(e.relatedTarget)) return;
175
+ focused.current = false;
176
+ resume();
177
+ }
121
178
  },
122
179
  /* @__PURE__ */ React.createElement(
123
180
  Icon,
@@ -126,19 +183,33 @@ function ToastItem(props) {
126
183
  className: cx("aura-toast__icon", t.loading && "aura-spin")
127
184
  }
128
185
  ),
129
- /* @__PURE__ */ React.createElement("div", { className: "aura-toast__body" }, /* @__PURE__ */ React.createElement("p", { className: "aura-toast__title" }, t.title), t.description ? /* @__PURE__ */ React.createElement("p", { className: "aura-toast__text" }, t.description) : null),
130
- t.action ? /* @__PURE__ */ React.createElement(
131
- "button",
132
- {
133
- type: "button",
134
- className: "aura-toast__action",
135
- onClick: function() {
136
- t.action.onClick && t.action.onClick();
137
- toast.dismiss(t.id);
138
- }
139
- },
140
- t.action.label
141
- ) : null,
186
+ /* @__PURE__ */ React.createElement("div", { className: "aura-toast__body" }, /* @__PURE__ */ React.createElement("p", { className: "aura-toast__title" }, t.title), rich ? /* @__PURE__ */ React.createElement("div", { className: "aura-toast__text" }, t.description) : null),
187
+ actions.length ? /* @__PURE__ */ React.createElement("div", { className: "aura-toast__actions" }, actions.map(function(a, i) {
188
+ return a.href ? /* @__PURE__ */ React.createElement(
189
+ Link,
190
+ {
191
+ key: i,
192
+ href: a.href,
193
+ className: "aura-toast__action",
194
+ onClick: function(e) {
195
+ if (plainClick(e)) run(a);
196
+ else if (a.onClick) a.onClick();
197
+ }
198
+ },
199
+ a.label
200
+ ) : /* @__PURE__ */ React.createElement(
201
+ "button",
202
+ {
203
+ key: i,
204
+ type: "button",
205
+ className: "aura-toast__action",
206
+ onClick: function() {
207
+ run(a);
208
+ }
209
+ },
210
+ a.label
211
+ );
212
+ })) : null,
142
213
  /* @__PURE__ */ React.createElement(
143
214
  IconButton,
144
215
  {
@@ -152,7 +223,22 @@ function ToastItem(props) {
152
223
  )
153
224
  );
154
225
  }
155
- function Toaster(props) {
226
+ function hotkeyMatcher(spec) {
227
+ const parts = spec.split("+").map(function(p) {
228
+ return p.trim().toLowerCase();
229
+ });
230
+ const key = parts[parts.length - 1] || "";
231
+ const code = /^[a-z]$/.test(key) ? "Key" + key.toUpperCase() : /^[0-9]$/.test(key) ? "Digit" + key : null;
232
+ const want = function(m) {
233
+ return parts.indexOf(m) >= 0 && parts.indexOf(m) < parts.length - 1;
234
+ };
235
+ return function(e) {
236
+ if (e.altKey !== want("alt") || e.ctrlKey !== want("ctrl") || e.shiftKey !== want("shift") || e.metaKey !== (want("meta") || want("cmd")))
237
+ return false;
238
+ return code ? e.code === code : e.key.toLowerCase() === key;
239
+ };
240
+ }
241
+ function Toaster(props = {}) {
156
242
  const t = useStrings();
157
243
  const mounted = useMounted();
158
244
  const s = React.useState(toastState.list);
@@ -165,15 +251,49 @@ function Toaster(props) {
165
251
  });
166
252
  };
167
253
  }, []);
254
+ const region = React.useRef(null);
255
+ const hotkey = props.hotkey === void 0 ? "Alt+T" : props.hotkey;
256
+ React.useEffect(
257
+ function() {
258
+ if (!hotkey) return;
259
+ const match = hotkeyMatcher(hotkey);
260
+ function onKey(e) {
261
+ if (!e.key || e.repeat || e.isComposing || !match(e) || !region.current) return;
262
+ const el = e.target;
263
+ const editable = !!el && (el.isContentEditable || /^(INPUT|TEXTAREA)$/.test(el.tagName || ""));
264
+ const mac = /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent || "");
265
+ if (editable && mac && e.altKey && !e.ctrlKey && !e.metaKey && e.key.length === 1) return;
266
+ const items = region.current.querySelectorAll(".aura-toast");
267
+ const last = items[items.length - 1];
268
+ if (!last) return;
269
+ const target = last.querySelector(".aura-toast__action") || last.querySelector("button");
270
+ if (!target) return;
271
+ e.preventDefault();
272
+ const from = outsideToaster(document.activeElement);
273
+ if (from) hotkeyOrigin.set(last, from);
274
+ target.focus();
275
+ }
276
+ document.addEventListener("keydown", onKey);
277
+ return function() {
278
+ document.removeEventListener("keydown", onKey);
279
+ };
280
+ },
281
+ [hotkey]
282
+ );
168
283
  if (!mounted) return null;
284
+ const pos = props.position || "bottom";
285
+ const offset = props.offset;
169
286
  return createPortal(
170
287
  /* @__PURE__ */ React.createElement(
171
288
  "div",
172
289
  {
173
- className: cx("aura-toaster", props && props.position === "top" && "is-top"),
290
+ ref: region,
291
+ className: cx("aura-toaster", pos.indexOf("top") === 0 && "is-top", /center$/.test(pos) && "is-center"),
292
+ style: offset != null ? { "--aura-toaster-offset": typeof offset === "number" ? offset + "px" : offset } : void 0,
174
293
  role: "region",
175
294
  "aria-live": "polite",
176
- "aria-label": t.notifications
295
+ "aria-label": hotkey ? t.notifications + " (" + hotkey + ")" : t.notifications,
296
+ "aria-keyshortcuts": hotkey || void 0
177
297
  },
178
298
  s[0].map(function(t2) {
179
299
  return /* @__PURE__ */ React.createElement(ToastItem, { key: t2.id, toast: t2 });
package/dist/index.d.cts CHANGED
@@ -124,6 +124,11 @@ export interface DataTableProps<Row extends Record<string, any> = Record<string,
124
124
  onSortChange?: ((sort: DataTableSort | null) => void) | undefined;
125
125
  /** Adds a checkbox column. */
126
126
  selectable?: boolean | undefined;
127
+ /** Which rows can be selected (5.6). A row it rejects shows no checkbox (the cell stays, so columns line up), is
128
+ * skipped by select-all, Space and the count, and never appears in `onSelectionChange` — even if passed in. */
129
+ isRowSelectable?: RowCallback<boolean, Row> | undefined;
130
+ /** Accessible name for a rejected row's empty cell, e.g. `(r) => 'Not awaiting review'` (5.6). */
131
+ rowSelectDisabledLabel?: RowCallback<string, Row> | undefined;
127
132
  /** Controlled selection (row keys); pair with onSelectionChange. */
128
133
  selected?: Array<string | number> | undefined;
129
134
  /** Initial selection when uncontrolled. */
@@ -305,6 +310,9 @@ export interface MenuProps {
305
310
  autoFocus?: boolean | undefined;
306
311
  /** Router link for items with `href`; defaults to AuraProvider's `linkComponent`, then `<a>`. (4.19) */
307
312
  linkComponent?: React$1.ElementType | undefined;
313
+ /** Content above the items (5.7), e.g. who is signed in. Not an item: arrow keys skip it; it is the menu's
314
+ * accessible description, read once when the menu opens. */
315
+ header?: React$1.ReactNode | undefined;
308
316
  }
309
317
  export interface FieldProps {
310
318
  /** Visible label; also the accessible name. */
@@ -487,14 +495,23 @@ export interface AlertProps {
487
495
  onDismiss?: (() => void) | undefined;
488
496
  className?: string | undefined;
489
497
  }
498
+ /** A button (or, with `href`, a link through AuraProvider's `linkComponent`) on a toast (5.6: `href`, `dismiss`). */
499
+ export interface ToastAction {
500
+ label: string;
501
+ onClick?: (() => void) | undefined;
502
+ /** Makes the action a link (routes through `linkComponent`, so `next/link` navigates client-side). */
503
+ href?: string | undefined;
504
+ /** Default true: the toast closes when the action runs. `false` keeps it (a list of items to work through). */
505
+ dismiss?: boolean | undefined;
506
+ }
490
507
  export interface ToastOptions {
491
508
  title: string;
492
- description?: string | undefined;
509
+ /** Text, or (5.6) rich content — lines with their own links, read once inside the toast's live region. */
510
+ description?: React$1.ReactNode | undefined;
493
511
  tone?: FeedbackTone | undefined;
494
- action?: {
495
- label: string;
496
- onClick?: (() => void) | undefined;
497
- } | undefined;
512
+ action?: ToastAction | undefined;
513
+ /** Up to two actions in a row (5.6); used instead of `action` when given. */
514
+ actions?: ToastAction[] | undefined;
498
515
  /** ms before it closes itself. Default 5000; Infinity keeps it until dismissed. */
499
516
  duration?: number | undefined;
500
517
  /** Reuse an id to replace a toast in place (same position, timer restarts) — never a second toast. */
@@ -533,6 +550,9 @@ export interface DialogProps {
533
550
  size?: "sm" | "md" | "lg" | undefined;
534
551
  /** Default true. False hides the close button and ignores Escape and scrim clicks. */
535
552
  dismissible?: boolean | undefined;
553
+ /** Whether a click on the scrim closes it (5.7). Default true, false for `role="alertdialog"`: a stray click
554
+ * outside shouldn't throw away a typed reason. Escape and the close button still close it. */
555
+ dismissOnScrim?: boolean | undefined;
536
556
  /** Use `alertdialog` for destructive confirmations. */
537
557
  role?: "dialog" | "alertdialog" | undefined;
538
558
  /** Default true. Turn off only for static demos. */
@@ -624,6 +644,7 @@ export interface SideNavProps {
624
644
  }
625
645
  export interface BreadcrumbProps {
626
646
  /** Root first; the last item is the current page. */
647
+ /** An item with neither `href` nor `onClick` is plain text (5.7), for a segment with no page of its own. */
627
648
  items: Array<{
628
649
  label: string;
629
650
  href?: string | undefined;
@@ -651,6 +672,9 @@ export interface DropdownMenuProps {
651
672
  label?: string | undefined;
652
673
  /** Router link for items with `href`. (4.19) */
653
674
  linkComponent?: React$1.ElementType | undefined;
675
+ /** Content above the items (5.7), e.g. name, email and role in an account menu. Not an item: arrow keys skip
676
+ * it; it is the menu's accessible description (`aria-describedby`). */
677
+ header?: React$1.ReactNode | undefined;
654
678
  }
655
679
  export interface ComboboxOption {
656
680
  value: string;
@@ -851,6 +875,8 @@ export interface DrawerProps {
851
875
  footer?: React$1.ReactNode | undefined;
852
876
  /** Scrim click and Escape close it. Default true. */
853
877
  dismissible?: boolean | undefined;
878
+ /** Whether a click on the scrim closes it (5.7). Default true; Escape and the close button still close it. */
879
+ dismissOnScrim?: boolean | undefined;
854
880
  /** Default true: focus moves to the element with `data-autofocus`, else the first control in the body. False only for static demos. */
855
881
  autoFocus?: boolean | undefined;
856
882
  /** Needed when there is no title. */
@@ -891,7 +917,8 @@ export interface AppShellProps {
891
917
  children?: React$1.ReactNode | undefined;
892
918
  navLabel?: string | undefined;
893
919
  menuLabel?: string | undefined;
894
- /** id of `<main>`, for a skip link. Default `main`. */
920
+ /** id of `<main>`, for a skip link. Default `main`. `<main>` has `tabIndex={-1}` (5.7), so a skip link or
921
+ * `document.getElementById(mainId).focus()` moves focus there (no ring is drawn on it). */
895
922
  mainId?: string | undefined;
896
923
  /** A BottomNav for phones (4.19). Shown below its `hideFrom` breakpoint; the content keeps room for it, and a
897
924
  * viewport ActionBar sits on top of it. With a `bottomNav` and no `nav`, there is no menu button. */
@@ -965,6 +992,9 @@ export interface BottomNavItem {
965
992
  badge?: boolean | undefined;
966
993
  /** Read after the label when `badge` is set, e.g. "new". */
967
994
  badgeLabel?: string | undefined;
995
+ /** The full name for screen readers when `label` is shortened to fit (5.7), e.g. label "Konto", ariaLabel
996
+ * "Mitt konto". Include the visible label in it (WCAG 2.5.3). The count or badge is still appended. */
997
+ ariaLabel?: string | undefined;
968
998
  }
969
999
  export interface BottomNavProps {
970
1000
  /** 2–5 items. */
@@ -1285,10 +1315,18 @@ export declare namespace toast {
1285
1315
  var dismiss: (id: string) => void;
1286
1316
  }
1287
1317
  export interface ToasterProps {
1288
- /** Default `bottom`. */
1289
- position?: "bottom" | "top" | undefined;
1290
- }
1291
- export declare function Toaster(props: ToasterProps): React$1.ReactElement | null;
1318
+ /** Default `bottom` (bottom right). `top` is top right; `top-center` / `bottom-center` centre the stack (5.6).
1319
+ * Below 640px toasts span the width either way. */
1320
+ position?: "bottom" | "bottom-right" | "bottom-center" | "top" | "top-right" | "top-center" | undefined;
1321
+ /** Distance from the top or bottom edge (5.6), e.g. `64` to clear a 56px top bar; px or any CSS length. Sets
1322
+ * `--aura-toaster-offset`; the safe-area inset is added on top. Default 24px (16px below 640px). */
1323
+ offset?: number | string | undefined;
1324
+ /** Keyboard shortcut to the newest toast (5.6): focuses its action, else its close button, and pauses its timer;
1325
+ * Escape closes it and returns focus. Default `"Alt+T"` (matched on the physical key, so macOS Option+T works);
1326
+ * `false` turns it off. */
1327
+ hotkey?: string | false | undefined;
1328
+ }
1329
+ export declare function Toaster(props?: ToasterProps): React$1.ReactElement | null;
1292
1330
  export declare const PasswordField: React$1.ForwardRefExoticComponent<PasswordFieldProps & React$1.RefAttributes<HTMLInputElement>>;
1293
1331
  export declare const FormErrorSummary: React$1.ForwardRefExoticComponent<FormErrorSummaryProps & React$1.RefAttributes<HTMLDivElement>>;
1294
1332
  export declare const FilterBar: React$1.ForwardRefExoticComponent<FilterBarProps & React$1.RefAttributes<HTMLDivElement>>;