@jirawatpyk/aura-react 5.15.0 → 5.16.1

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 CHANGED
@@ -186,6 +186,19 @@ DataTable with sort and page in the URL (4.17): `onStateChange={({ sort, page })
186
186
  - **Time zone**: `timeZone` on AuraProvider, DatePicker, DateRangePicker and Calendar decides "today" (the marker, `min`/`max="today"`, the first month shown); or pass `today` as an ISO date. `todayIn('Asia/Bangkok')` is exported from the root and `/server`.
187
187
  - **Toasts** queue past three instead of dropping: six in a row all show, in order, three at a time.
188
188
 
189
+ ## 5.16.1 — Select ground (Chamber-OS 109)
190
+
191
+ - **A custom Select keeps the input ground.** The read-only rule for text fields matched the Select's trigger button too (`:read-only` matches anything not editable), so every custom Select showed the disabled ground and looked disabled. It now applies to a read-only `input` only; a read-only TextField still takes the disabled ground, and a read-only Textarea now does too.
192
+
193
+ ## 5.16 — Chamber-OS addenda 15–16, the larger items
194
+
195
+ - **SideNav action rows** (item 95): an item with `selectable: false` is an action row — Sign out, Help — with the rows' look, 44px on touch screens and the arrow keys, that runs `onSelect` and never becomes the current item (no `aria-current`, no `onChange`). In the rail it is its icon, the label its name. Any item can take `onSelect`. `collapseToggle="row"` shows `collapsible`'s toggle as a labelled row ("Collapse sidebar" / "Expand sidebar") instead of an icon button. In AppShell's phone drawer an action row closes the drawer; `onAction(id)` on SideNav reports action rows.
196
+ - **SideNav `chevron="right"` and a one-row header** (item 96): `chevron="right"` points a closed group's chevron right (left in right-to-left pages) and down when open; the default stays down / up. The header's end padding is now 8px (was 16px) for every SideNav, so a brand, its dot and a badge share one row at 240px — a visible change to headers that were close to the edge.
197
+ - **DataTable `rangeSelect` and how a selection changed** (item 98): `onSelectionChange(keys, change)` now gets a second argument, `{ key, shiftKey, source: 'click' | 'keyboard' | 'all' | 'sync', range? }` — existing one-argument handlers are unaffected (a wrapper that calls a `DataTableProps['onSelectionChange']` itself now needs to pass the second argument). With `rangeSelect`, Shift-clicking a row's box (or Shift+Space on a row) sets every selectable row on this page, from the last row changed to this one in the order shown, to this row's new state; `range` lists them.
198
+ - **Dialog `trigger`, `finalFocus`, `onCloseComplete` and attributes on the panel** (item 101): `trigger={<Button>Add contact</Button>}` renders the button with `aria-haspopup="dialog"` and `aria-expanded`; without `open`, the Dialog then opens and closes itself (`onOpen` / `onClose` still report it). `finalFocus` (a ref or a function) sends focus there when it closes — by a button, Escape or the scrim — instead of back to the opener, falling back to the opener if it returns nothing. `onCloseComplete` runs once after the panel has left the page (also when an open Dialog is unmounted). Inside a self-managed Dialog, `useDialogClose()` gives Cancel and Save a way to close it. `id`, `data-*`, `aria-*`, `style`, `lang` and `dir` reach the `.aura-dialog` panel, as on Drawer; Drawer takes `finalFocus` and `onCloseComplete` too. `open` and `onClose` are optional now.
199
+ - The whole-library and IIFE budgets are 62 kB (were 60).
200
+ - **Combobox `allowCustomValue` and `groups`** (item 105): with `allowCustomValue` a typed value that isn't in the list is kept — on Enter when no option is highlighted, on Tab, or on leaving the field; text matching an option's label picks that option, empty text clears the value. `groups={[{ label, options }]}` lists options under headings (after any `options`, which is optional now), each a named group for screen readers; filtering hides a heading with no matches. With `allowCustomValue` typing highlights nothing (unless the text is an option's label), so Enter and Tab keep what was typed; an option reached with the arrow keys is picked by Enter or Tab. `clearable={false}` keeps the value when the text is emptied.
201
+
189
202
  ## 5.15 — Chamber-OS addenda 14–16, layout
190
203
 
191
204
  - **Table `stackStyle="cards"`** (item 85): below `stackBelow` each row is its own framed card (DataTable's card radius, border, padding and 8px gap), with no frame around them; above it the table is unchanged. The table then sits in one more `div` (`.aura-tbl-cards`), which carries the width query. Table roles, labels and the `card` slots behave as before.
@@ -138,6 +138,7 @@ window.Aura = (() => {
138
138
  useBreakpoint: () => useBreakpoint,
139
139
  useColorScheme: () => useColorScheme,
140
140
  useDensity: () => useDensity,
141
+ useDialogClose: () => useDialogClose,
141
142
  useFormatDate: () => useFormatDate,
142
143
  useResponsive: () => useResponsive
143
144
  });
@@ -2466,6 +2467,13 @@ window.Aura = (() => {
2466
2467
  const density = useDensity();
2467
2468
  const auto = uid(), id = props.id || auto, listId = id + "-list";
2468
2469
  const options = (props.options || []).map(toOpt);
2470
+ const groups = props.groups || [];
2471
+ groups.forEach(function(g, gi) {
2472
+ g.options.forEach(function(o) {
2473
+ options.push(Object.assign({}, toOpt(o), { __g: gi }));
2474
+ });
2475
+ });
2476
+ const custom = !multi && !!props.allowCustomValue;
2469
2477
  const st = useMaybeControlled(
2470
2478
  multi ? mp.value : props.value === void 0 ? void 0 : props.value == null ? [] : [props.value],
2471
2479
  multi ? mp.defaultValue || [] : props.defaultValue == null ? [] : [props.defaultValue],
@@ -2505,7 +2513,9 @@ window.Aura = (() => {
2505
2513
  });
2506
2514
  const more = shown.length > limit;
2507
2515
  shown = shown.slice(0, limit);
2508
- const activeIdx = Math.min(active, shown.length - 1);
2516
+ const activeIdx = active < 0 ? query ? shown.findIndex(function(o) {
2517
+ return !o.disabled && norm(o.label) === norm(query.trim());
2518
+ }) : -1 : Math.min(active, shown.length - 1);
2509
2519
  function place() {
2510
2520
  if (!boxRef.current) return;
2511
2521
  const r = boxRef.current.getBoundingClientRect();
@@ -2530,6 +2540,7 @@ window.Aura = (() => {
2530
2540
  function outside(e) {
2531
2541
  if (boxRef.current && boxRef.current.contains(e.target)) return;
2532
2542
  if (listRef.current && listRef.current.contains(e.target)) return;
2543
+ if (commitRef.current()) return;
2533
2544
  close(false);
2534
2545
  }
2535
2546
  function onScroll(e) {
@@ -2566,6 +2577,27 @@ window.Aura = (() => {
2566
2577
  setOpen(false);
2567
2578
  setQuery(null);
2568
2579
  }
2580
+ const committed = React19.useRef(false);
2581
+ const commitRef = React19.useRef(function() {
2582
+ return false;
2583
+ });
2584
+ function commitTyped() {
2585
+ if (!custom || query == null || committed.current) return false;
2586
+ committed.current = true;
2587
+ const q = query.trim();
2588
+ if (!q) {
2589
+ if (props.clearable !== false) setValue(null);
2590
+ } else {
2591
+ const hit = options.filter(function(o) {
2592
+ return !o.disabled && norm(o.label) === norm(q);
2593
+ })[0];
2594
+ setValue(hit ? hit.value : q);
2595
+ }
2596
+ setQuery(null);
2597
+ setOpen(false);
2598
+ return true;
2599
+ }
2600
+ commitRef.current = commitTyped;
2569
2601
  function choose(o) {
2570
2602
  if (!o || o.disabled) return;
2571
2603
  if (multi) {
@@ -2606,7 +2638,7 @@ window.Aura = (() => {
2606
2638
  if (open && live[activeIdx]) {
2607
2639
  e.preventDefault();
2608
2640
  choose(live[activeIdx]);
2609
- }
2641
+ } else if (commitTyped()) e.preventDefault();
2610
2642
  } else if (k === "Escape") {
2611
2643
  if (open) {
2612
2644
  e.preventDefault();
@@ -2618,58 +2650,95 @@ window.Aura = (() => {
2618
2650
  } else if (k === "Backspace" && multi && !props.readOnly && !text2 && values.length) {
2619
2651
  setValues(values.slice(0, -1));
2620
2652
  } else if (k === "Tab") {
2653
+ if (custom && open && query != null && live[activeIdx]) {
2654
+ choose(live[activeIdx]);
2655
+ return;
2656
+ }
2657
+ if (commitTyped()) return;
2621
2658
  if (open) close();
2622
2659
  }
2623
2660
  }
2624
- const text2 = query != null ? query : selected ? selected.label : "";
2661
+ const text2 = query != null ? query : selected ? selected.label : custom && value != null ? value : "";
2625
2662
  const summaryId = id + "-picked";
2626
2663
  const optId = function(i) {
2627
2664
  return id + "-opt-" + i;
2628
2665
  };
2666
+ function renderOpt(o, i) {
2667
+ const isSel = multi ? isPicked(o.value) : !!selected && o.value === selected.value;
2668
+ const blocked = o.disabled || multi && full && !isSel;
2669
+ return /* @__PURE__ */ React19.createElement(
2670
+ "li",
2671
+ {
2672
+ key: o.value,
2673
+ id: optId(i),
2674
+ role: "option",
2675
+ "data-idx": i,
2676
+ "aria-selected": isSel,
2677
+ "aria-disabled": blocked || void 0,
2678
+ className: cx(
2679
+ "aura-combo__option",
2680
+ i === activeIdx && "is-active",
2681
+ isSel && "is-selected",
2682
+ blocked && "is-disabled"
2683
+ ),
2684
+ onPointerDown: function(e) {
2685
+ e.preventDefault();
2686
+ },
2687
+ onClick: function() {
2688
+ if (!blocked || isSel) choose(o);
2689
+ },
2690
+ onPointerMove: function() {
2691
+ if (activeIdx !== i) setActive(i);
2692
+ }
2693
+ },
2694
+ o.icon ? /* @__PURE__ */ React19.createElement(Icon, { name: o.icon }) : null,
2695
+ /* @__PURE__ */ React19.createElement("span", { className: "aura-combo__text" }, /* @__PURE__ */ React19.createElement("span", { className: "aura-combo__label" }, o.label), o.description ? /* @__PURE__ */ React19.createElement("span", { className: "aura-combo__desc" }, o.description) : null),
2696
+ isSel ? /* @__PURE__ */ React19.createElement(Icon, { name: /* @__PURE__ */ React19.createElement(IconCheck, null), className: "aura-combo__check" }) : null
2697
+ );
2698
+ }
2699
+ function grouped(list2) {
2700
+ const out = [];
2701
+ list2.forEach(function(o, i) {
2702
+ const last = out[out.length - 1];
2703
+ if (last && last.g === o.__g) last.items.push([o, i]);
2704
+ else out.push({ g: o.__g, items: [[o, i]] });
2705
+ });
2706
+ return out;
2707
+ }
2629
2708
  const list = open && mounted && posState[0] ? (0, import_react_dom3.createPortal)(
2630
- /* @__PURE__ */ React19.createElement("div", { ref: listRef, "data-density": density, className: "aura-combo__popover", style: posState[0] }, live.length ? /* @__PURE__ */ React19.createElement(
2631
- "ul",
2709
+ /* @__PURE__ */ React19.createElement(
2710
+ "div",
2632
2711
  {
2633
- id: listId,
2634
- role: "listbox",
2635
- "aria-label": props.label,
2636
- "aria-multiselectable": multi || void 0,
2637
- className: "aura-combo__list"
2712
+ ref: listRef,
2713
+ "data-density": density,
2714
+ className: "aura-combo__popover",
2715
+ style: posState[0],
2716
+ onPointerDown: function(e) {
2717
+ e.preventDefault();
2718
+ }
2638
2719
  },
2639
- live.map(function(o, i) {
2640
- const isSel = multi ? isPicked(o.value) : !!selected && o.value === selected.value;
2641
- const blocked = o.disabled || multi && full && !isSel;
2642
- return /* @__PURE__ */ React19.createElement(
2643
- "li",
2644
- {
2645
- key: o.value,
2646
- id: optId(i),
2647
- role: "option",
2648
- "data-idx": i,
2649
- "aria-selected": isSel,
2650
- "aria-disabled": blocked || void 0,
2651
- className: cx(
2652
- "aura-combo__option",
2653
- i === activeIdx && "is-active",
2654
- isSel && "is-selected",
2655
- blocked && "is-disabled"
2656
- ),
2657
- onPointerDown: function(e) {
2658
- e.preventDefault();
2659
- },
2660
- onClick: function() {
2661
- if (!blocked || isSel) choose(o);
2662
- },
2663
- onPointerMove: function() {
2664
- if (activeIdx !== i) setActive(i);
2665
- }
2666
- },
2667
- o.icon ? /* @__PURE__ */ React19.createElement(Icon, { name: o.icon }) : null,
2668
- /* @__PURE__ */ React19.createElement("span", { className: "aura-combo__text" }, /* @__PURE__ */ React19.createElement("span", { className: "aura-combo__label" }, o.label), o.description ? /* @__PURE__ */ React19.createElement("span", { className: "aura-combo__desc" }, o.description) : null),
2669
- isSel ? /* @__PURE__ */ React19.createElement(Icon, { name: /* @__PURE__ */ React19.createElement(IconCheck, null), className: "aura-combo__check" }) : null
2670
- );
2671
- })
2672
- ) : null, props.loading || !live.length || more ? /* @__PURE__ */ React19.createElement("div", { className: "aura-combo__list", role: "status" }, props.loading ? /* @__PURE__ */ React19.createElement("div", { className: "aura-combo__note" }, /* @__PURE__ */ React19.createElement(Icon, { name: /* @__PURE__ */ React19.createElement(IconLoaderCircle, null), className: "aura-spin" }), props.loadingText || t.searching) : !live.length ? /* @__PURE__ */ React19.createElement("div", { className: "aura-combo__note" }, props.emptyText || t.noMatches) : /* @__PURE__ */ React19.createElement("div", { className: "aura-combo__note" }, t.keepTyping((props.options || []).length))) : null),
2720
+ live.length ? /* @__PURE__ */ React19.createElement(
2721
+ "ul",
2722
+ {
2723
+ id: listId,
2724
+ role: "listbox",
2725
+ "aria-label": props.label,
2726
+ "aria-multiselectable": multi || void 0,
2727
+ className: "aura-combo__list"
2728
+ },
2729
+ grouped(live).map(function(seg) {
2730
+ if (seg.g == null)
2731
+ return /* @__PURE__ */ React19.createElement(React19.Fragment, { key: "u" + seg.items[0][1] }, seg.items.map(function(p) {
2732
+ return renderOpt(p[0], p[1]);
2733
+ }));
2734
+ const gid = id + "-group-" + seg.g + "-" + seg.items[0][1];
2735
+ return /* @__PURE__ */ React19.createElement("li", { key: gid, role: "group", "aria-labelledby": gid, className: "aura-combo__group" }, /* @__PURE__ */ React19.createElement("span", { id: gid, role: "presentation", className: "aura-combo__group-label" }, groups[seg.g].label), /* @__PURE__ */ React19.createElement("ul", { role: "none", className: "aura-combo__grouplist" }, seg.items.map(function(p) {
2736
+ return renderOpt(p[0], p[1]);
2737
+ })));
2738
+ })
2739
+ ) : null,
2740
+ props.loading || !live.length || more ? /* @__PURE__ */ React19.createElement("div", { className: "aura-combo__list", role: "status" }, props.loading ? /* @__PURE__ */ React19.createElement("div", { className: "aura-combo__note" }, /* @__PURE__ */ React19.createElement(Icon, { name: /* @__PURE__ */ React19.createElement(IconLoaderCircle, null), className: "aura-spin" }), props.loadingText || t.searching) : !live.length ? /* @__PURE__ */ React19.createElement("div", { className: "aura-combo__note" }, props.emptyText || t.noMatches) : /* @__PURE__ */ React19.createElement("div", { className: "aura-combo__note" }, t.keepTyping(options.length))) : null
2741
+ ),
2673
2742
  document.body
2674
2743
  ) : null;
2675
2744
  return /* @__PURE__ */ React19.createElement(
@@ -2731,8 +2800,9 @@ window.Aura = (() => {
2731
2800
  required: props.required && (!multi || !values.length),
2732
2801
  value: text2,
2733
2802
  onChange: function(e) {
2803
+ committed.current = false;
2734
2804
  setQuery(e.target.value);
2735
- setActive(0);
2805
+ setActive(custom ? -1 : 0);
2736
2806
  if (!open) setOpen(true);
2737
2807
  if (props.onSearch) props.onSearch(e.target.value);
2738
2808
  },
@@ -2741,6 +2811,8 @@ window.Aura = (() => {
2741
2811
  onBlur: function() {
2742
2812
  setTimeout(function() {
2743
2813
  if (listRef.current && listRef.current.contains(document.activeElement)) return;
2814
+ if (custom && document.activeElement === inputRef.current) return;
2815
+ if (commitRef.current()) return;
2744
2816
  setOpen(false);
2745
2817
  setQuery(null);
2746
2818
  }, 0);
@@ -4270,6 +4342,8 @@ window.Aura = (() => {
4270
4342
  const mounted = useMounted();
4271
4343
  const prev = React27.useRef(null);
4272
4344
  const o = opts || {};
4345
+ const latest = React27.useRef(o);
4346
+ latest.current = o;
4273
4347
  React27.useEffect(
4274
4348
  function() {
4275
4349
  if (!open || !mounted) return;
@@ -4290,7 +4364,12 @@ window.Aura = (() => {
4290
4364
  if (target && o.autoFocus !== false) target.focus();
4291
4365
  return function() {
4292
4366
  unlock();
4293
- if (prev.current && prev.current.focus) prev.current.focus();
4367
+ const l = latest.current;
4368
+ const ff = l.finalFocus;
4369
+ const to = ff ? typeof ff === "function" ? ff() : ff.current : null;
4370
+ const back = to && to.isConnected ? to : prev.current;
4371
+ if (back && back.focus) back.focus();
4372
+ if (l.onCloseComplete) l.onCloseComplete();
4294
4373
  };
4295
4374
  },
4296
4375
  [open, mounted]
@@ -4672,22 +4751,77 @@ window.Aura = (() => {
4672
4751
  // src/Dialog.tsx
4673
4752
  var React30 = __toESM(require_react(), 1);
4674
4753
  var import_react_dom8 = __toESM(require_react_dom(), 1);
4754
+ var DialogClose = React30.createContext(null);
4755
+ function useDialogClose() {
4756
+ return React30.useContext(DialogClose) || function() {
4757
+ };
4758
+ }
4675
4759
  var Dialog = React30.forwardRef(function Dialog2(props, ref) {
4676
4760
  const t = useStrings();
4677
4761
  const density = useDensity();
4678
4762
  const own = React30.useRef(null), merged = useMergedRef(ref, own), titleId = uid(), descId = uid();
4763
+ const openState = useMaybeControlled(props.open, false, null), open = openState[0];
4764
+ if (props.open === void 0 && !props.trigger)
4765
+ devWarnOnce("dialog-no-open", "Dialog has neither `open` nor `trigger`, so it can never open. Pass one of them.");
4766
+ const trigId = uid();
4767
+ function closeNow() {
4768
+ if (props.open === void 0) openState[1](false);
4769
+ if (props.onClose) props.onClose();
4770
+ }
4679
4771
  function close() {
4680
- if (props.dismissible !== false && props.onClose) props.onClose();
4772
+ if (props.dismissible === false) return;
4773
+ closeNow();
4681
4774
  }
4682
4775
  const scrimCloses = props.dismissible !== false && (props.dismissOnScrim !== void 0 ? props.dismissOnScrim : props.role !== "alertdialog");
4683
- const modal = useModal(props.open, own, {
4776
+ const modal = useModal(open, own, {
4684
4777
  autoFocus: props.autoFocus,
4685
4778
  onEscape: close,
4686
4779
  bodySelector: ".aura-dialog__body",
4687
- footSelector: ".aura-dialog__foot"
4780
+ footSelector: ".aura-dialog__foot",
4781
+ /* With a trigger, focus goes back to it even where a click doesn't focus buttons (Safari). */
4782
+ finalFocus: props.trigger ? function() {
4783
+ const ff = props.finalFocus;
4784
+ const to = ff ? typeof ff === "function" ? ff() : ff.current : null;
4785
+ return to || document.querySelector('[data-aura-trigger="' + trigId + '"]');
4786
+ } : props.finalFocus,
4787
+ onCloseComplete: props.onCloseComplete
4688
4788
  });
4689
- if (!modal.ready) return null;
4690
- return (0, import_react_dom8.createPortal)(
4789
+ const trig = props.trigger;
4790
+ const trigger = trig && React30.isValidElement(trig) ? React30.cloneElement(trig, {
4791
+ "aria-haspopup": "dialog",
4792
+ "aria-expanded": open,
4793
+ "data-aura-trigger": trigId,
4794
+ onClick: function(e) {
4795
+ const own2 = trig.props.onClick;
4796
+ if (own2) own2(e);
4797
+ if (e.defaultPrevented) return;
4798
+ if (props.open === void 0) openState[1](true);
4799
+ if (props.onOpen) props.onOpen();
4800
+ }
4801
+ }) : null;
4802
+ if (!modal.ready) return trigger;
4803
+ const rest = omit(props, [
4804
+ "open",
4805
+ "onClose",
4806
+ "title",
4807
+ "description",
4808
+ "children",
4809
+ "footer",
4810
+ "size",
4811
+ "dismissible",
4812
+ "dismissOnScrim",
4813
+ "role",
4814
+ "autoFocus",
4815
+ "className",
4816
+ "trigger",
4817
+ "onOpen",
4818
+ "finalFocus",
4819
+ "onCloseComplete",
4820
+ "aria-labelledby",
4821
+ "aria-describedby",
4822
+ "aria-modal"
4823
+ ]);
4824
+ const panel = (0, import_react_dom8.createPortal)(
4691
4825
  /* @__PURE__ */ React30.createElement("div", { className: "aura-dialog-layer", "data-density": density, onKeyDown: modal.onKeyDown }, /* @__PURE__ */ React30.createElement(
4692
4826
  "div",
4693
4827
  {
@@ -4701,21 +4835,22 @@ window.Aura = (() => {
4701
4835
  ), /* @__PURE__ */ React30.createElement(
4702
4836
  "div",
4703
4837
  {
4838
+ ...rest,
4704
4839
  ref: merged,
4705
4840
  role: props.role || "dialog",
4706
4841
  "aria-modal": true,
4707
4842
  "aria-labelledby": titleId,
4708
- "aria-describedby": props.description ? descId : void 0,
4843
+ "aria-describedby": cx(props.description ? descId : "", props["aria-describedby"]) || void 0,
4709
4844
  tabIndex: -1,
4710
4845
  className: cx("aura-dialog", "aura-dialog--" + (props.size || "md"), props.className)
4711
4846
  },
4712
4847
  /* @__PURE__ */ React30.createElement("div", { className: "aura-dialog__head" }, /* @__PURE__ */ React30.createElement("h2", { className: "aura-dialog__title", id: titleId }, props.title), props.dismissible !== false ? /* @__PURE__ */ React30.createElement(IconButton, { icon: /* @__PURE__ */ React30.createElement(IconX, null), label: t.close, className: "aura-dialog__close", onClick: close }) : null),
4713
4848
  props.description ? /* @__PURE__ */ React30.createElement("p", { className: "aura-dialog__desc", id: descId }, props.description) : null,
4714
- props.children ? /* @__PURE__ */ React30.createElement("div", { className: "aura-dialog__body" }, props.children) : null,
4715
- props.footer ? /* @__PURE__ */ React30.createElement("div", { className: "aura-dialog__foot" }, props.footer) : null
4849
+ /* @__PURE__ */ React30.createElement(DialogClose.Provider, { value: closeNow }, props.children ? /* @__PURE__ */ React30.createElement("div", { className: "aura-dialog__body" }, props.children) : null, props.footer ? /* @__PURE__ */ React30.createElement("div", { className: "aura-dialog__foot" }, props.footer) : null)
4716
4850
  )),
4717
4851
  document.body
4718
4852
  );
4853
+ return trigger ? /* @__PURE__ */ React30.createElement(React30.Fragment, null, trigger, panel) : panel;
4719
4854
  });
4720
4855
  var Drawer = React30.forwardRef(function Drawer2(props, ref) {
4721
4856
  const t = useStrings();
@@ -4728,7 +4863,9 @@ window.Aura = (() => {
4728
4863
  autoFocus: props.autoFocus,
4729
4864
  onEscape: close,
4730
4865
  bodySelector: ".aura-drawer__body",
4731
- footSelector: ".aura-drawer__foot"
4866
+ footSelector: ".aura-drawer__foot",
4867
+ finalFocus: props.finalFocus,
4868
+ onCloseComplete: props.onCloseComplete
4732
4869
  });
4733
4870
  if (!modal.ready) return null;
4734
4871
  const side = props.side === "left" ? "left" : "right";
@@ -4749,7 +4886,9 @@ window.Aura = (() => {
4749
4886
  "aria-labelledby",
4750
4887
  "className",
4751
4888
  "closeLabel",
4752
- "closeProps"
4889
+ "closeProps",
4890
+ "finalFocus",
4891
+ "onCloseComplete"
4753
4892
  ]);
4754
4893
  const drawerScrimCloses = props.dismissible !== false && props.dismissOnScrim !== false;
4755
4894
  return (0, import_react_dom8.createPortal)(
@@ -4845,7 +4984,9 @@ window.Aura = (() => {
4845
4984
  oneCallback ? null : props.onSortChange
4846
4985
  );
4847
4986
  const sort = sortState[0], setSort = sortState[1];
4848
- const selState = useMaybeControlled(props.selected, props.defaultSelected || [], props.onSelectionChange);
4987
+ const selState = useMaybeControlled(props.selected, props.defaultSelected || [], null);
4988
+ const onSel = React31.useRef(props.onSelectionChange);
4989
+ onSel.current = props.onSelectionChange;
4849
4990
  const canSelect = function(r) {
4850
4991
  return !!r && (!props.isRowSelectable || !!props.isRowSelectable(r));
4851
4992
  };
@@ -4856,7 +4997,11 @@ window.Aura = (() => {
4856
4997
  });
4857
4998
  const selected = selState[0].filter(function(k) {
4858
4999
  return !rejected[String(k)];
4859
- }), setSelected = selState[1];
5000
+ });
5001
+ function setSelected(next, change) {
5002
+ selState[1](next);
5003
+ if (onSel.current) onSel.current(next, change || { key: null, shiftKey: false, source: "sync" });
5004
+ }
4860
5005
  const droppedSig = selState[0].length === selected.length ? "" : "#" + selState[0].filter(function(k) {
4861
5006
  return rejected[String(k)];
4862
5007
  }).join("\0") + "#" + selected.join("\0");
@@ -5344,11 +5489,39 @@ window.Aura = (() => {
5344
5489
  return selSet[String(k)];
5345
5490
  }).length;
5346
5491
  const all = nSel > 0 && nSel === pageKeys.length;
5347
- function toggle(k, on) {
5492
+ const anchor = React31.useRef(null);
5493
+ const shiftClick = React31.useRef(false);
5494
+ function toggle(k, on, how) {
5495
+ const same2 = function(x) {
5496
+ return String(x) === String(k);
5497
+ };
5498
+ let range = null;
5499
+ if (props.rangeSelect && how.shiftKey && anchor.current != null) {
5500
+ const from = pageKeys.findIndex(function(x) {
5501
+ return String(x) === String(anchor.current);
5502
+ });
5503
+ const to = pageKeys.findIndex(same2);
5504
+ if (from >= 0 && to >= 0) range = pageKeys.slice(Math.min(from, to), Math.max(from, to) + 1);
5505
+ }
5506
+ anchor.current = k;
5507
+ const change = { key: k, shiftKey: how.shiftKey, source: how.source };
5508
+ if (range) {
5509
+ const inRange = {};
5510
+ range.forEach(function(x) {
5511
+ inRange[String(x)] = true;
5512
+ });
5513
+ const rest = selected.filter(function(x) {
5514
+ return !inRange[String(x)];
5515
+ });
5516
+ change.range = range;
5517
+ setSelected(on ? rest.concat(range) : rest, change);
5518
+ return;
5519
+ }
5348
5520
  setSelected(
5349
5521
  on ? selected.concat([k]) : selected.filter(function(x) {
5350
- return String(x) !== String(k);
5351
- })
5522
+ return !same2(x);
5523
+ }),
5524
+ change
5352
5525
  );
5353
5526
  }
5354
5527
  function toggleAll() {
@@ -5361,7 +5534,8 @@ window.Aura = (() => {
5361
5534
  pageKeys.filter(function(k) {
5362
5535
  return !selSet[String(k)];
5363
5536
  })
5364
- )
5537
+ ),
5538
+ { key: null, shiftKey: false, source: "all" }
5365
5539
  );
5366
5540
  }
5367
5541
  function canResize(c) {
@@ -5540,8 +5714,8 @@ window.Aura = (() => {
5540
5714
  };
5541
5715
  }).concat([{ separator: true }, { label: t.resetColumns, icon: /* @__PURE__ */ React31.createElement(IconRotateCcw, null), onSelect: resetColumns }]);
5542
5716
  }
5543
- function openMenu(kind, key, anchor, rc) {
5544
- setMenu({ kind, key, anchor, rc });
5717
+ function openMenu(kind, key, anchor2, rc) {
5718
+ setMenu({ kind, key, anchor: anchor2, rc });
5545
5719
  }
5546
5720
  function closeMenu(restore) {
5547
5721
  const m = menu;
@@ -5601,7 +5775,8 @@ window.Aura = (() => {
5601
5775
  if (pageKeys.length) toggleAll();
5602
5776
  } else if (r === 0 && col && col.sortable && canSortAny) sortBy(col.key);
5603
5777
  else if (r > 0 && k === " " && selectable) {
5604
- if (canSelect(row) && !(stacked && noCardSel)) toggle(row[rowKey], !selSet[row[rowKey]]);
5778
+ if (canSelect(row) && !(stacked && noCardSel))
5779
+ toggle(row[rowKey], !selSet[row[rowKey]], { shiftKey: e.shiftKey, source: "keyboard" });
5605
5780
  } else if (r > 0 && k === "Enter" && rowHref && !target.querySelector("button, a[href]:not(.aura-table__row-link), input, select, textarea"))
5606
5781
  followRow(target.closest('[role="row"]'), e);
5607
5782
  else if (r > 0 && k === "Enter" && target.querySelector("button, a[href], input, select, textarea"))
@@ -5791,6 +5966,9 @@ window.Aura = (() => {
5791
5966
  "aria-label": canSelect(r) ? selLabel : props.rowSelectDisabledLabel ? props.rowSelectDisabledLabel(r) : void 0,
5792
5967
  onFocus: function(e) {
5793
5968
  if (e.target === e.currentTarget) activeState[1]({ r: ri, c: 0 });
5969
+ },
5970
+ onClickCapture: function(e) {
5971
+ shiftClick.current = e.shiftKey;
5794
5972
  }
5795
5973
  },
5796
5974
  canSelect(r) ? /* @__PURE__ */ React31.createElement(
@@ -5801,7 +5979,9 @@ window.Aura = (() => {
5801
5979
  tabIndex: -1,
5802
5980
  label: selLabel,
5803
5981
  onChange: function(on) {
5804
- toggle(k, on);
5982
+ const shift = shiftClick.current;
5983
+ shiftClick.current = false;
5984
+ toggle(k, on, { shiftKey: shift, source: "click" });
5805
5985
  }
5806
5986
  }
5807
5987
  ) : null
@@ -6511,16 +6691,19 @@ window.Aura = (() => {
6511
6691
  return item(c, depth + 1);
6512
6692
  })));
6513
6693
  }
6514
- const on = active === it.id;
6694
+ const action = it.selectable === false;
6695
+ const on = !action && active === it.id;
6515
6696
  const common = {
6516
6697
  ...tipHandlers(it.label),
6517
- className: cx("aura-nav__item", on && "is-active"),
6698
+ className: cx("aura-nav__item", action && "aura-nav__item--action", on && "is-active"),
6518
6699
  "aria-current": on ? "page" : void 0,
6519
6700
  style: depth ? { ["--aura-nav-depth"]: depth } : void 0,
6520
6701
  onClick: function(e) {
6521
6702
  if (!it.href) e.preventDefault();
6522
6703
  else if (!plainClick(e)) return;
6523
- st[1](it.id);
6704
+ if (!action) st[1](it.id);
6705
+ if (it.onSelect) it.onSelect();
6706
+ if (action && props.onAction) props.onAction(it.id);
6524
6707
  }
6525
6708
  };
6526
6709
  return /* @__PURE__ */ React34.createElement("li", { key: it.id }, it.href ? /* @__PURE__ */ React34.createElement(Link, { href: it.href, ...common }, inner(it, false, false)) : /* @__PURE__ */ React34.createElement("button", { type: "button", ...common }, inner(it, false, false)));
@@ -6537,6 +6720,7 @@ window.Aura = (() => {
6537
6720
  "aura-nav",
6538
6721
  collapsed && "aura-nav--collapsed",
6539
6722
  props.bordered === false && "aura-nav--borderless",
6723
+ props.chevron === "right" && "aura-nav--chevron-right",
6540
6724
  props.className
6541
6725
  ),
6542
6726
  "data-collapsed": collapsed ? "" : void 0,
@@ -6550,7 +6734,20 @@ window.Aura = (() => {
6550
6734
  })));
6551
6735
  })),
6552
6736
  props.footer ? /* @__PURE__ */ React34.createElement("div", { className: "aura-nav__footer" }, props.footer) : null,
6553
- props.collapsible ? /* @__PURE__ */ React34.createElement("div", { className: "aura-nav__toggle" }, /* @__PURE__ */ React34.createElement(
6737
+ props.collapsible && props.collapseToggle === "row" ? /* @__PURE__ */ React34.createElement("div", { className: "aura-nav__toggle aura-nav__toggle--row" }, /* @__PURE__ */ React34.createElement(
6738
+ "button",
6739
+ {
6740
+ type: "button",
6741
+ className: "aura-nav__item aura-nav__item--action",
6742
+ ...tipHandlers(collapsed ? t.expandNav : t.collapseNav),
6743
+ onClick: function() {
6744
+ setTip(null);
6745
+ col[1](!collapsed);
6746
+ }
6747
+ },
6748
+ /* @__PURE__ */ React34.createElement(Icon, { name: collapsed ? /* @__PURE__ */ React34.createElement(IconPanelLeftOpen, null) : /* @__PURE__ */ React34.createElement(IconPanelLeftClose, null) }),
6749
+ /* @__PURE__ */ React34.createElement("span", { className: "aura-nav__label" }, collapsed ? t.expandNav : t.collapseNav)
6750
+ )) : props.collapsible ? /* @__PURE__ */ React34.createElement("div", { className: "aura-nav__toggle" }, /* @__PURE__ */ React34.createElement(
6554
6751
  IconButton,
6555
6752
  {
6556
6753
  icon: collapsed ? /* @__PURE__ */ React34.createElement(IconPanelLeftOpen, null) : /* @__PURE__ */ React34.createElement(IconPanelLeftClose, null),
@@ -6817,6 +7014,11 @@ window.Aura = (() => {
6817
7014
  onNav(id);
6818
7015
  setOpen(false);
6819
7016
  },
7017
+ /* 5.16: an action row (Sign out) closes the drawer too. */
7018
+ onAction: function(id) {
7019
+ setOpen(false);
7020
+ if (navEl.props.onAction) navEl.props.onAction(id);
7021
+ },
6820
7022
  className: cx(navEl.props.className, "is-in-drawer"),
6821
7023
  collapsed: false,
6822
7024
  collapsible: false