@jirawatpyk/aura-react 5.5.0 → 5.6.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.
@@ -3061,10 +3061,35 @@ toast.dismiss = function(id) {
3061
3061
  promote();
3062
3062
  emitToasts();
3063
3063
  };
3064
+ var hotkeyOrigin = /* @__PURE__ */ new WeakMap();
3065
+ function outsideToaster(el) {
3066
+ return el && el !== document.body && !el.closest(".aura-toaster") ? el : null;
3067
+ }
3064
3068
  function ToastItem(props) {
3065
3069
  const str = useStrings();
3070
+ const Link = useLinkComponent();
3071
+ const node = React19.useRef(null);
3072
+ const origin = React19.useRef(null);
3073
+ useIsoLayoutEffect(function() {
3074
+ origin.current = outsideToaster(document.activeElement);
3075
+ const el = node.current;
3076
+ return function() {
3077
+ if (!el || !el.contains(document.activeElement)) return;
3078
+ const back = [hotkeyOrigin.get(el), origin.current].filter(function(b) {
3079
+ return !!b && b.isConnected;
3080
+ })[0];
3081
+ hotkeyOrigin.delete(el);
3082
+ setTimeout(function() {
3083
+ if (back && back.isConnected && (!document.activeElement || document.activeElement === document.body))
3084
+ back.focus();
3085
+ }, 0);
3086
+ };
3087
+ }, []);
3066
3088
  const t = props.toast, timer = React19.useRef(null), left = React19.useRef(t.duration || 5e3), since = React19.useRef(0);
3067
- const hover = React19.useRef(false);
3089
+ const hover = React19.useRef(false), focused = React19.useRef(false);
3090
+ function syncFocus() {
3091
+ if (focused.current && !(node.current && node.current.contains(document.activeElement))) focused.current = false;
3092
+ }
3068
3093
  function start() {
3069
3094
  if (left.current === Infinity || timer.current) return;
3070
3095
  since.current = Date.now();
@@ -3073,11 +3098,10 @@ function ToastItem(props) {
3073
3098
  }, left.current);
3074
3099
  }
3075
3100
  function resume() {
3076
- hover.current = false;
3077
- start();
3101
+ syncFocus();
3102
+ if (!hover.current && !focused.current) start();
3078
3103
  }
3079
3104
  function pause() {
3080
- hover.current = true;
3081
3105
  if (timer.current) {
3082
3106
  clearTimeout(timer.current);
3083
3107
  timer.current = null;
@@ -3089,23 +3113,56 @@ function ToastItem(props) {
3089
3113
  if (timer.current) clearTimeout(timer.current);
3090
3114
  timer.current = null;
3091
3115
  left.current = t.duration || 5e3;
3092
- if (!hover.current) start();
3116
+ syncFocus();
3117
+ if (!hover.current && !focused.current) start();
3093
3118
  return function() {
3094
3119
  if (timer.current) clearTimeout(timer.current);
3095
3120
  };
3096
3121
  },
3097
3122
  [t.rev]
3098
3123
  );
3124
+ const rich = t.description != null && typeof t.description !== "boolean" && t.description !== "";
3125
+ const actions = (t.actions && t.actions.length ? t.actions : t.action ? [t.action] : []).slice(0, 2);
3126
+ function run(a) {
3127
+ if (a.onClick) a.onClick();
3128
+ if (a.dismiss !== false) toast.dismiss(t.id);
3129
+ }
3099
3130
  return /* @__PURE__ */ React19.createElement(
3100
3131
  "div",
3101
3132
  {
3102
- className: cx("aura-toast", "aura-toast--" + t.tone, t.loading && "is-loading"),
3133
+ ref: node,
3134
+ onKeyDown: function(e) {
3135
+ if (e.key === "Escape") {
3136
+ e.stopPropagation();
3137
+ toast.dismiss(t.id);
3138
+ }
3139
+ },
3140
+ className: cx(
3141
+ "aura-toast",
3142
+ "aura-toast--" + t.tone,
3143
+ t.loading && "is-loading",
3144
+ /* Two actions, or one beside a description, go on their own row under the text so it keeps its width. */
3145
+ (actions.length > 1 || actions.length && rich) && "has-action-row"
3146
+ ),
3103
3147
  role: t.tone === "danger" ? "alert" : "status",
3104
3148
  "aria-busy": t.loading || void 0,
3105
- onMouseEnter: pause,
3106
- onMouseLeave: resume,
3107
- onFocus: pause,
3108
- onBlur: resume
3149
+ onMouseEnter: function() {
3150
+ hover.current = true;
3151
+ pause();
3152
+ },
3153
+ onMouseLeave: function() {
3154
+ hover.current = false;
3155
+ resume();
3156
+ },
3157
+ onFocus: function() {
3158
+ focused.current = true;
3159
+ pause();
3160
+ },
3161
+ onBlur: function(e) {
3162
+ if (node.current && e.relatedTarget && node.current.contains(e.relatedTarget)) return;
3163
+ focused.current = false;
3164
+ resume();
3165
+ }
3109
3166
  },
3110
3167
  /* @__PURE__ */ React19.createElement(
3111
3168
  Icon,
@@ -3114,19 +3171,33 @@ function ToastItem(props) {
3114
3171
  className: cx("aura-toast__icon", t.loading && "aura-spin")
3115
3172
  }
3116
3173
  ),
3117
- /* @__PURE__ */ React19.createElement("div", { className: "aura-toast__body" }, /* @__PURE__ */ React19.createElement("p", { className: "aura-toast__title" }, t.title), t.description ? /* @__PURE__ */ React19.createElement("p", { className: "aura-toast__text" }, t.description) : null),
3118
- t.action ? /* @__PURE__ */ React19.createElement(
3119
- "button",
3120
- {
3121
- type: "button",
3122
- className: "aura-toast__action",
3123
- onClick: function() {
3124
- t.action.onClick && t.action.onClick();
3125
- toast.dismiss(t.id);
3126
- }
3127
- },
3128
- t.action.label
3129
- ) : null,
3174
+ /* @__PURE__ */ React19.createElement("div", { className: "aura-toast__body" }, /* @__PURE__ */ React19.createElement("p", { className: "aura-toast__title" }, t.title), rich ? /* @__PURE__ */ React19.createElement("div", { className: "aura-toast__text" }, t.description) : null),
3175
+ actions.length ? /* @__PURE__ */ React19.createElement("div", { className: "aura-toast__actions" }, actions.map(function(a, i) {
3176
+ return a.href ? /* @__PURE__ */ React19.createElement(
3177
+ Link,
3178
+ {
3179
+ key: i,
3180
+ href: a.href,
3181
+ className: "aura-toast__action",
3182
+ onClick: function(e) {
3183
+ if (plainClick(e)) run(a);
3184
+ else if (a.onClick) a.onClick();
3185
+ }
3186
+ },
3187
+ a.label
3188
+ ) : /* @__PURE__ */ React19.createElement(
3189
+ "button",
3190
+ {
3191
+ key: i,
3192
+ type: "button",
3193
+ className: "aura-toast__action",
3194
+ onClick: function() {
3195
+ run(a);
3196
+ }
3197
+ },
3198
+ a.label
3199
+ );
3200
+ })) : null,
3130
3201
  /* @__PURE__ */ React19.createElement(
3131
3202
  IconButton,
3132
3203
  {
@@ -3140,7 +3211,22 @@ function ToastItem(props) {
3140
3211
  )
3141
3212
  );
3142
3213
  }
3143
- function Toaster(props) {
3214
+ function hotkeyMatcher(spec) {
3215
+ const parts = spec.split("+").map(function(p) {
3216
+ return p.trim().toLowerCase();
3217
+ });
3218
+ const key = parts[parts.length - 1] || "";
3219
+ const code = /^[a-z]$/.test(key) ? "Key" + key.toUpperCase() : /^[0-9]$/.test(key) ? "Digit" + key : null;
3220
+ const want = function(m) {
3221
+ return parts.indexOf(m) >= 0 && parts.indexOf(m) < parts.length - 1;
3222
+ };
3223
+ return function(e) {
3224
+ if (e.altKey !== want("alt") || e.ctrlKey !== want("ctrl") || e.shiftKey !== want("shift") || e.metaKey !== (want("meta") || want("cmd")))
3225
+ return false;
3226
+ return code ? e.code === code : e.key.toLowerCase() === key;
3227
+ };
3228
+ }
3229
+ function Toaster(props = {}) {
3144
3230
  const t = useStrings();
3145
3231
  const mounted = useMounted();
3146
3232
  const s = React19.useState(toastState.list);
@@ -3153,15 +3239,49 @@ function Toaster(props) {
3153
3239
  });
3154
3240
  };
3155
3241
  }, []);
3242
+ const region = React19.useRef(null);
3243
+ const hotkey = props.hotkey === void 0 ? "Alt+T" : props.hotkey;
3244
+ React19.useEffect(
3245
+ function() {
3246
+ if (!hotkey) return;
3247
+ const match = hotkeyMatcher(hotkey);
3248
+ function onKey(e) {
3249
+ if (!e.key || e.repeat || e.isComposing || !match(e) || !region.current) return;
3250
+ const el = e.target;
3251
+ const editable = !!el && (el.isContentEditable || /^(INPUT|TEXTAREA)$/.test(el.tagName || ""));
3252
+ const mac = /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent || "");
3253
+ if (editable && mac && e.altKey && !e.ctrlKey && !e.metaKey && e.key.length === 1) return;
3254
+ const items2 = region.current.querySelectorAll(".aura-toast");
3255
+ const last = items2[items2.length - 1];
3256
+ if (!last) return;
3257
+ const target = last.querySelector(".aura-toast__action") || last.querySelector("button");
3258
+ if (!target) return;
3259
+ e.preventDefault();
3260
+ const from = outsideToaster(document.activeElement);
3261
+ if (from) hotkeyOrigin.set(last, from);
3262
+ target.focus();
3263
+ }
3264
+ document.addEventListener("keydown", onKey);
3265
+ return function() {
3266
+ document.removeEventListener("keydown", onKey);
3267
+ };
3268
+ },
3269
+ [hotkey]
3270
+ );
3156
3271
  if (!mounted) return null;
3272
+ const pos = props.position || "bottom";
3273
+ const offset = props.offset;
3157
3274
  return (0, import_react_dom5.createPortal)(
3158
3275
  /* @__PURE__ */ React19.createElement(
3159
3276
  "div",
3160
3277
  {
3161
- className: cx("aura-toaster", props && props.position === "top" && "is-top"),
3278
+ ref: region,
3279
+ className: cx("aura-toaster", pos.indexOf("top") === 0 && "is-top", /center$/.test(pos) && "is-center"),
3280
+ style: offset != null ? { "--aura-toaster-offset": typeof offset === "number" ? offset + "px" : offset } : void 0,
3162
3281
  role: "region",
3163
3282
  "aria-live": "polite",
3164
- "aria-label": t.notifications
3283
+ "aria-label": hotkey ? t.notifications + " (" + hotkey + ")" : t.notifications,
3284
+ "aria-keyshortcuts": hotkey || void 0
3165
3285
  },
3166
3286
  s[0].map(function(t2) {
3167
3287
  return /* @__PURE__ */ React19.createElement(ToastItem, { key: t2.id, toast: t2 });
@@ -3989,7 +4109,27 @@ var DataTableImpl = React28.forwardRef(function DataTable(props, ref) {
3989
4109
  );
3990
4110
  const sort = sortState[0], setSort = sortState[1];
3991
4111
  const selState = useMaybeControlled(props.selected, props.defaultSelected || [], props.onSelectionChange);
3992
- const selected = selState[0], setSelected = selState[1];
4112
+ const canSelect = function(r) {
4113
+ return !!r && (!props.isRowSelectable || !!props.isRowSelectable(r));
4114
+ };
4115
+ const rejected = {};
4116
+ if (props.isRowSelectable)
4117
+ rows.forEach(function(r) {
4118
+ if (!canSelect(r)) rejected[String(r[rowKey])] = true;
4119
+ });
4120
+ const selected = selState[0].filter(function(k) {
4121
+ return !rejected[String(k)];
4122
+ }), setSelected = selState[1];
4123
+ const droppedSig = selState[0].length === selected.length ? "" : "#" + selState[0].filter(function(k) {
4124
+ return rejected[String(k)];
4125
+ }).join("\0") + "#" + selected.join("\0");
4126
+ React28.useEffect(
4127
+ function() {
4128
+ if (droppedSig !== "") setSelected(selected);
4129
+ },
4130
+ // eslint-disable-next-line react-hooks/exhaustive-deps
4131
+ [droppedSig]
4132
+ );
3993
4133
  const pageState = useMaybeControlled(
3994
4134
  props.page,
3995
4135
  props.defaultPage || 1,
@@ -4400,7 +4540,7 @@ var DataTableImpl = React28.forwardRef(function DataTable(props, ref) {
4400
4540
  el.focus();
4401
4541
  }
4402
4542
  }
4403
- const pageKeys = pageRows.map(function(r) {
4543
+ const pageKeys = pageRows.filter(canSelect).map(function(r) {
4404
4544
  return r[rowKey];
4405
4545
  });
4406
4546
  const selSet = {};
@@ -4664,10 +4804,12 @@ var DataTableImpl = React28.forwardRef(function DataTable(props, ref) {
4664
4804
  if (inner) inner.focus();
4665
4805
  else handled = false;
4666
4806
  } else if (k === " " || k === "Enter") {
4667
- if (r === 0 && !col && selectable && nRows) toggleAll();
4668
- else if (r === 0 && col && col.sortable && canSortAny) sortBy(col.key);
4669
- else if (r > 0 && k === " " && selectable) toggle(row[rowKey], !selSet[row[rowKey]]);
4670
- else if (r > 0 && k === "Enter" && rowHref && !target.querySelector("button, a[href]:not(.aura-table__row-link), input, select, textarea"))
4807
+ if (r === 0 && !col && selectable && nRows) {
4808
+ if (pageKeys.length) toggleAll();
4809
+ } else if (r === 0 && col && col.sortable && canSortAny) sortBy(col.key);
4810
+ else if (r > 0 && k === " " && selectable) {
4811
+ if (canSelect(row)) toggle(row[rowKey], !selSet[row[rowKey]]);
4812
+ } else if (r > 0 && k === "Enter" && rowHref && !target.querySelector("button, a[href]:not(.aura-table__row-link), input, select, textarea"))
4671
4813
  followRow(target.closest('[role="row"]'), e);
4672
4814
  else if (r > 0 && k === "Enter" && target.querySelector("button, a[href], input, select, textarea"))
4673
4815
  target.querySelector("button, a[href], input, select, textarea").focus();
@@ -4696,7 +4838,7 @@ var DataTableImpl = React28.forwardRef(function DataTable(props, ref) {
4696
4838
  activeState[1]({ r: 0, c: 0 });
4697
4839
  }
4698
4840
  },
4699
- nRows ? /* @__PURE__ */ React28.createElement(
4841
+ pageKeys.length ? /* @__PURE__ */ React28.createElement(
4700
4842
  Checkbox,
4701
4843
  {
4702
4844
  hideLabel: true,
@@ -4707,7 +4849,7 @@ var DataTableImpl = React28.forwardRef(function DataTable(props, ref) {
4707
4849
  onChange: toggleAll
4708
4850
  }
4709
4851
  ) : null,
4710
- nRows ? /* @__PURE__ */ React28.createElement("span", { className: "aura-table__sel-text" }, nSel ? t.selectedCount(nSel) : t.selectAll) : /* @__PURE__ */ React28.createElement("span", { className: "aura-sr-only" }, t.selectRows)
4852
+ pageKeys.length ? /* @__PURE__ */ React28.createElement("span", { className: "aura-table__sel-text" }, nSel ? t.selectedCount(nSel) : t.selectAll) : /* @__PURE__ */ React28.createElement("span", { className: "aura-sr-only" }, t.selectRows)
4711
4853
  )
4712
4854
  );
4713
4855
  vis.forEach(function(c, i) {
@@ -4845,12 +4987,12 @@ var DataTableImpl = React28.forwardRef(function DataTable(props, ref) {
4845
4987
  className: cx("aura-table__sel", nPinned && "is-pinned"),
4846
4988
  tabIndex: tabFor(ri, 0),
4847
4989
  "data-rc": ri + ":0",
4848
- "aria-label": t.selectRow(k),
4990
+ "aria-label": canSelect(r) ? t.selectRow(k) : props.rowSelectDisabledLabel ? props.rowSelectDisabledLabel(r) : void 0,
4849
4991
  onFocus: function(e) {
4850
4992
  if (e.target === e.currentTarget) activeState[1]({ r: ri, c: 0 });
4851
4993
  }
4852
4994
  },
4853
- /* @__PURE__ */ React28.createElement(
4995
+ canSelect(r) ? /* @__PURE__ */ React28.createElement(
4854
4996
  Checkbox,
4855
4997
  {
4856
4998
  hideLabel: true,
@@ -4861,7 +5003,7 @@ var DataTableImpl = React28.forwardRef(function DataTable(props, ref) {
4861
5003
  toggle(k, on);
4862
5004
  }
4863
5005
  }
4864
- )
5006
+ ) : null
4865
5007
  )
4866
5008
  );
4867
5009
  vis.forEach(function(c, j) {
@@ -4964,7 +5106,7 @@ var DataTableImpl = React28.forwardRef(function DataTable(props, ref) {
4964
5106
  key: k,
4965
5107
  role: "row",
4966
5108
  "aria-rowindex": first + i + 2,
4967
- "aria-selected": selectable ? isSel : void 0,
5109
+ "aria-selected": selectable && canSelect(r) ? isSel : void 0,
4968
5110
  className: cx(
4969
5111
  "aura-table__row",
4970
5112
  isSel && "is-selected",
@@ -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",