@ai-matrx/design-system 0.86.349 → 0.86.350

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.
Files changed (91) hide show
  1. package/CHANGELOG.md +17 -2
  2. package/dist/carousel.cjs.map +1 -1
  3. package/dist/carousel.js +5 -5
  4. package/dist/chunk-4WSXAP24.js +51 -0
  5. package/dist/chunk-4WSXAP24.js.map +1 -0
  6. package/dist/{chunk-XTP6HOB6.js → chunk-6XNYYROM.js} +1 -1
  7. package/dist/chunk-6XNYYROM.js.map +1 -0
  8. package/dist/{chunk-54UK6P7E.js → chunk-BCGNEWZM.js} +3 -3
  9. package/dist/{chunk-CEA6X445.js → chunk-CFUXEGW2.js} +2 -2
  10. package/dist/{chunk-BC7LMEOD.js → chunk-GJ6UGQ4X.js} +7 -3
  11. package/dist/chunk-GJ6UGQ4X.js.map +1 -0
  12. package/dist/{chunk-6QKT52FR.js → chunk-MTE6N7XU.js} +2 -2
  13. package/dist/{chunk-S6F34AX5.js → chunk-QA7EHFRX.js} +2 -2
  14. package/dist/{chunk-KYH42WEX.js → chunk-SC3XPADZ.js} +3 -3
  15. package/dist/{chunk-CJBSBKG5.js → chunk-SOCNWQMX.js} +2 -2
  16. package/dist/{chunk-GLLPO66A.js → chunk-U46XUQI7.js} +2 -2
  17. package/dist/{chunk-6CDAHINE.js → chunk-UTADJKVR.js} +2 -2
  18. package/dist/{chunk-TWRC7NP4.js → chunk-WUD23YTG.js} +4 -4
  19. package/dist/{chunk-MIJMP4WI.js → chunk-YRF22M5S.js} +6 -1
  20. package/dist/{chunk-MIJMP4WI.js.map → chunk-YRF22M5S.js.map} +1 -1
  21. package/dist/clipboard-fallback-dialog.cjs.map +1 -1
  22. package/dist/clipboard-fallback-dialog.js +1 -1
  23. package/dist/controls.cjs.map +1 -1
  24. package/dist/controls.css +25 -0
  25. package/dist/controls.d.cts +2 -2
  26. package/dist/controls.d.ts +2 -2
  27. package/dist/controls.js +5 -5
  28. package/dist/data-table/data-row-inspector.cjs.map +1 -1
  29. package/dist/data-table/data-row-inspector.js +4 -4
  30. package/dist/data-table/data-row-window.cjs.map +1 -1
  31. package/dist/data-table/data-row-window.js +5 -5
  32. package/dist/data-table/index.cjs +8 -5
  33. package/dist/data-table/index.cjs.map +1 -1
  34. package/dist/data-table/index.js +9 -9
  35. package/dist/data-table/pagination.cjs.map +1 -1
  36. package/dist/data-table/pagination.js +2 -2
  37. package/dist/data-table/spreadsheet.cjs +6 -2
  38. package/dist/data-table/spreadsheet.cjs.map +1 -1
  39. package/dist/data-table/spreadsheet.d.cts +4 -1
  40. package/dist/data-table/spreadsheet.d.ts +4 -1
  41. package/dist/data-table/spreadsheet.js +3 -1
  42. package/dist/data-table/uuid-cell.cjs.map +1 -1
  43. package/dist/data-table/uuid-cell.js +2 -2
  44. package/dist/desktop.cjs +93 -12
  45. package/dist/desktop.cjs.map +1 -1
  46. package/dist/desktop.css +27 -0
  47. package/dist/desktop.d.cts +6 -5
  48. package/dist/desktop.d.ts +6 -5
  49. package/dist/desktop.js +64 -15
  50. package/dist/desktop.js.map +1 -1
  51. package/dist/index.cjs +17 -7
  52. package/dist/index.cjs.map +1 -1
  53. package/dist/index.d.cts +13 -4
  54. package/dist/index.d.ts +13 -4
  55. package/dist/index.js +21 -14
  56. package/dist/index.js.map +1 -1
  57. package/dist/item.cjs.map +1 -1
  58. package/dist/item.js +2 -2
  59. package/dist/lite-table.cjs.map +1 -1
  60. package/dist/lite-table.js +1 -1
  61. package/dist/notice-stack.cjs +70 -0
  62. package/dist/notice-stack.cjs.map +1 -0
  63. package/dist/notice-stack.d.cts +60 -0
  64. package/dist/notice-stack.d.ts +60 -0
  65. package/dist/notice-stack.js +16 -0
  66. package/dist/notice-stack.js.map +1 -0
  67. package/dist/record-list.cjs.map +1 -1
  68. package/dist/record-list.js +2 -2
  69. package/dist/settings.cjs.map +1 -1
  70. package/dist/settings.js +3 -3
  71. package/dist/swipe-deck.cjs +95 -25
  72. package/dist/swipe-deck.cjs.map +1 -1
  73. package/dist/swipe-deck.css +14 -0
  74. package/dist/swipe-deck.d.cts +20 -0
  75. package/dist/swipe-deck.d.ts +20 -0
  76. package/dist/swipe-deck.js +87 -25
  77. package/dist/swipe-deck.js.map +1 -1
  78. package/dist/writing-field.cjs.map +1 -1
  79. package/dist/writing-field.js +2 -2
  80. package/package.json +12 -18
  81. package/dist/chunk-BC7LMEOD.js.map +0 -1
  82. package/dist/chunk-XTP6HOB6.js.map +0 -1
  83. /package/dist/{chunk-54UK6P7E.js.map → chunk-BCGNEWZM.js.map} +0 -0
  84. /package/dist/{chunk-CEA6X445.js.map → chunk-CFUXEGW2.js.map} +0 -0
  85. /package/dist/{chunk-6QKT52FR.js.map → chunk-MTE6N7XU.js.map} +0 -0
  86. /package/dist/{chunk-S6F34AX5.js.map → chunk-QA7EHFRX.js.map} +0 -0
  87. /package/dist/{chunk-KYH42WEX.js.map → chunk-SC3XPADZ.js.map} +0 -0
  88. /package/dist/{chunk-CJBSBKG5.js.map → chunk-SOCNWQMX.js.map} +0 -0
  89. /package/dist/{chunk-GLLPO66A.js.map → chunk-U46XUQI7.js.map} +0 -0
  90. /package/dist/{chunk-6CDAHINE.js.map → chunk-UTADJKVR.js.map} +0 -0
  91. /package/dist/{chunk-TWRC7NP4.js.map → chunk-WUD23YTG.js.map} +0 -0
@@ -547,6 +547,20 @@
547
547
  .matrx-sd .matrx-sd-review-change { opacity: 1; }
548
548
  }
549
549
  .matrx-sd-end-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 24px; }
550
+ /* the answered list, mid-deck: no mark, its title and the way back */
551
+ .matrx-sd .matrx-sd-review-back {
552
+ margin: 0; height: 40px; padding: 0 18px; border: 0; border-radius: 20px;
553
+ background: hsl(var(--primary)); color: hsl(var(--primary-foreground));
554
+ font: inherit; font-size: 15px; font-weight: 600;
555
+ }
556
+ @media (hover: hover) { .matrx-sd .matrx-sd-review-back:hover { filter: brightness(1.06); } }
557
+ .matrx-sd .matrx-sd-corner { position: relative; }
558
+ .matrx-sd .matrx-sd-corner[aria-pressed="true"] { color: var(--sd-fg); background: var(--sd-pill); }
559
+ .matrx-sd-corner-count {
560
+ position: absolute; top: 2px; right: 0; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px;
561
+ background: hsl(var(--primary)); color: hsl(var(--primary-foreground));
562
+ font-size: 11px; font-weight: 700; line-height: 18px; text-align: center; font-variant-numeric: tabular-nums;
563
+ }
550
564
  /* Nothing to answer: three outline cards, fanned, and the words under them. */
551
565
  .matrx-sd-empty { align-items: center; text-align: center; margin: auto; }
552
566
  .matrx-sd-empty .matrx-sd-end-hero { font-size: clamp(28px, min(7vw, 9cqh), 56px); }
@@ -22,6 +22,17 @@ interface DeckAnswer {
22
22
  /** Stable id: what results, callbacks and connections carry. */
23
23
  readonly id: string;
24
24
  readonly label: string;
25
+ /**
26
+ * Offered only through the card's own controls (`renderCardActions` / `renderCardBody` call
27
+ * `answer(id)`), never drawn as a button: an answer that needs a choice first ("Related ▸ Partner").
28
+ */
29
+ readonly hidden?: boolean;
30
+ /**
31
+ * "Skip for now": in a bound deck the connection moves the card to the back of the line and keeps no
32
+ * answer (it comes back once the rest are answered); undo brings it back to the front. In a deck with no
33
+ * connection it is kept like any other answer.
34
+ */
35
+ readonly requeue?: boolean;
25
36
  readonly tone: DeckTone;
26
37
  /** Higher is better. Decides which answer wins when categories disagree (default by direction). */
27
38
  readonly rank?: number;
@@ -289,6 +300,11 @@ interface DeckChange {
289
300
  readonly ops: readonly DeckOp[];
290
301
  readonly inverse: readonly DeckOp[];
291
302
  readonly from: DeckDirection | null;
303
+ /**
304
+ * The connection acts on it but never keeps it as an answer (a `requeue` answer: the card goes to the
305
+ * back of the line): once the connection resolves, the deck stops drawing it, so the card is unanswered.
306
+ */
307
+ readonly transient?: boolean;
292
308
  }
293
309
  declare function applyOp(state: DeckState, op: DeckOp): DeckState;
294
310
  declare function applyOps(state: DeckState, ops: readonly DeckOp[]): DeckState;
@@ -437,6 +453,10 @@ interface SwipeDeckLabels {
437
453
  readonly filterAnswers: string;
438
454
  /** The empty screen (no items). */
439
455
  readonly emptyTitle: string;
456
+ /** The corner that opens every answer so far, and that list's title. */
457
+ readonly answered: string;
458
+ /** Leave the answered list for the card in focus. */
459
+ readonly backToCards: string;
440
460
  }
441
461
  declare const DEFAULT_SWIPE_DECK_LABELS: SwipeDeckLabels;
442
462
 
@@ -22,6 +22,17 @@ interface DeckAnswer {
22
22
  /** Stable id: what results, callbacks and connections carry. */
23
23
  readonly id: string;
24
24
  readonly label: string;
25
+ /**
26
+ * Offered only through the card's own controls (`renderCardActions` / `renderCardBody` call
27
+ * `answer(id)`), never drawn as a button: an answer that needs a choice first ("Related ▸ Partner").
28
+ */
29
+ readonly hidden?: boolean;
30
+ /**
31
+ * "Skip for now": in a bound deck the connection moves the card to the back of the line and keeps no
32
+ * answer (it comes back once the rest are answered); undo brings it back to the front. In a deck with no
33
+ * connection it is kept like any other answer.
34
+ */
35
+ readonly requeue?: boolean;
25
36
  readonly tone: DeckTone;
26
37
  /** Higher is better. Decides which answer wins when categories disagree (default by direction). */
27
38
  readonly rank?: number;
@@ -289,6 +300,11 @@ interface DeckChange {
289
300
  readonly ops: readonly DeckOp[];
290
301
  readonly inverse: readonly DeckOp[];
291
302
  readonly from: DeckDirection | null;
303
+ /**
304
+ * The connection acts on it but never keeps it as an answer (a `requeue` answer: the card goes to the
305
+ * back of the line): once the connection resolves, the deck stops drawing it, so the card is unanswered.
306
+ */
307
+ readonly transient?: boolean;
292
308
  }
293
309
  declare function applyOp(state: DeckState, op: DeckOp): DeckState;
294
310
  declare function applyOps(state: DeckState, ops: readonly DeckOp[]): DeckState;
@@ -437,6 +453,10 @@ interface SwipeDeckLabels {
437
453
  readonly filterAnswers: string;
438
454
  /** The empty screen (no items). */
439
455
  readonly emptyTitle: string;
456
+ /** The corner that opens every answer so far, and that list's title. */
457
+ readonly answered: string;
458
+ /** Leave the answered list for the card in focus. */
459
+ readonly backToCards: string;
440
460
  }
441
461
  declare const DEFAULT_SWIPE_DECK_LABELS: SwipeDeckLabels;
442
462
 
@@ -15,6 +15,7 @@ import {
15
15
  CircleHelpIcon,
16
16
  CopyIcon,
17
17
  EraserIcon,
18
+ ListChecksIcon,
18
19
  Loader2Icon,
19
20
  Maximize2Icon,
20
21
  Minimize2Icon,
@@ -213,7 +214,7 @@ function EdgeButtons({ keyMap, suggested, refs, onAnswer, suggestionLabel, disab
213
214
  );
214
215
  }) });
215
216
  }
216
- function CornerButton({ corner, label, shortcut, onClick, disabled, children }) {
217
+ function CornerButton({ corner, label, shortcut, onClick, disabled, pressed, children }) {
217
218
  return (
218
219
  // ui-exception: the deck's four corner targets sit in its own frame geometry
219
220
  /* @__PURE__ */ jsx(
@@ -226,6 +227,7 @@ function CornerButton({ corner, label, shortcut, onClick, disabled, children })
226
227
  title: shortcut ? `${label} (${shortcut})` : label,
227
228
  "aria-keyshortcuts": shortcut,
228
229
  disabled,
230
+ "aria-pressed": pressed,
229
231
  onClick,
230
232
  children
231
233
  }
@@ -742,17 +744,17 @@ function useDeckController({ items, binding, initialState, onChange }) {
742
744
  const stateRef = React2.useRef(state);
743
745
  stateRef.current = state;
744
746
  const send = React2.useCallback(
745
- (ops, onRefused) => {
747
+ (ops, onRefused, transient = false) => {
746
748
  if (!binding) {
747
749
  setLocal((s) => applyOps(s, ops));
748
750
  return;
749
751
  }
750
752
  const id = ++seq.current;
751
- setPending((list2) => [...list2, { id, ops, settledAt: null }]);
753
+ setPending((list2) => [...list2, { id, ops, settledAt: null, ...transient ? { transient } : {} }]);
752
754
  binding.apply(ops).then(
753
755
  () => {
754
756
  setWriteError(null);
755
- setPending((list2) => list2.map((p) => p.id === id ? { ...p, settledAt: Date.now() } : p));
757
+ setPending((list2) => transient ? list2.filter((p) => p.id !== id) : list2.map((p) => p.id === id ? { ...p, settledAt: Date.now() } : p));
756
758
  },
757
759
  (err) => {
758
760
  setPending((list2) => list2.filter((p) => p.id !== id));
@@ -767,7 +769,7 @@ function useDeckController({ items, binding, initialState, onChange }) {
767
769
  (change) => {
768
770
  touched.current = true;
769
771
  setHistory((h) => [...h, change]);
770
- send(change.ops, () => setHistory((h) => h.filter((c) => c !== change)));
772
+ send(change.ops, () => setHistory((h) => h.filter((c) => c !== change)), change.transient);
771
773
  onChangeRef.current?.(applyOps(stateRef.current, change.ops), change, false);
772
774
  },
773
775
  [send]
@@ -779,7 +781,7 @@ function useDeckController({ items, binding, initialState, onChange }) {
779
781
  if (!last) return null;
780
782
  touched.current = true;
781
783
  setHistory((h) => h.slice(0, -1));
782
- send(last.inverse, () => setHistory((h) => [...h, last]));
784
+ send(last.inverse, () => setHistory((h) => [...h, last]), last.transient);
783
785
  onChangeRef.current?.(applyOps(stateRef.current, last.inverse), last, true);
784
786
  return last;
785
787
  }, [send]);
@@ -970,6 +972,8 @@ var DEFAULT_SWIPE_DECK_LABELS = {
970
972
  allDone: "All done",
971
973
  answeredOf: "{answered} of {total} answered",
972
974
  yourAnswers: "Your answers",
975
+ answered: "Answered",
976
+ backToCards: "Back to cards",
973
977
  change: "Change",
974
978
  rightAnswer: "Right answer",
975
979
  right: "Right",
@@ -1423,9 +1427,13 @@ function SwipeDeck(props) {
1423
1427
  const [bubble, setBubble] = React4.useState(null);
1424
1428
  const [toast, setToast] = React4.useState(null);
1425
1429
  const [said, setSaid] = React4.useState("");
1426
- const cardItem = step.kind === "card" ? step.item : null;
1430
+ const [reviewing, setReviewing] = React4.useState(false);
1431
+ const reviewingRef = React4.useRef(false);
1432
+ reviewingRef.current = reviewing;
1433
+ const cardItem = step.kind === "card" && !reviewing ? step.item : null;
1427
1434
  const cardKey = cardItem ? keyFor(rules, cardItem) : deckKey;
1428
1435
  const cardMore = React4.useMemo(() => cardItem ? moreFor(rules, cardItem) : [], [cardItem, rules]);
1436
+ const cardMoreShown = React4.useMemo(() => cardMore.filter((a) => !a.hidden), [cardMore]);
1429
1437
  const suggestedDir = cardItem ? directionOf(cardKey, cardItem.suggested) : null;
1430
1438
  const [cardDetail, setCardDetail] = React4.useState(null);
1431
1439
  const detailNow = cardItem ? cardDetail?.id === cardItem.id ? cardDetail.detail : state.details?.[cardItem.id] ?? null : null;
@@ -1468,7 +1476,8 @@ function SwipeDeck(props) {
1468
1476
  await flyOut(layerRef.current, dir ?? "down", speed);
1469
1477
  const now = stateRef.current;
1470
1478
  const rides = answerDetail(cardItem, detail === void 0 ? detailRef.current : detail);
1471
- const change = answerChange(now, cardItem, answer.id, rules, items, dir, rides);
1479
+ const made = answerChange(now, cardItem, answer.id, rules, items, dir, rides);
1480
+ const change = answer.requeue && props.binding ? { ...made, transient: true } : made;
1472
1481
  enterFrom.current = null;
1473
1482
  setCardDetail(null);
1474
1483
  setLocked(null);
@@ -1613,6 +1622,13 @@ function SwipeDeck(props) {
1613
1622
  const afterTour = React4.useRef(null);
1614
1623
  const runKeyRef = React4.useRef(() => false);
1615
1624
  runKeyRef.current = (key, target) => {
1625
+ if (reviewingRef.current) {
1626
+ if (key === "Escape") {
1627
+ setReviewing(false);
1628
+ return true;
1629
+ }
1630
+ if (key !== "z" && key !== "Z" && key !== "Backspace") return false;
1631
+ }
1616
1632
  const dir = KEYS[key];
1617
1633
  const shortcut = !dir && key.length === 1 ? shortcutRef.current(key) : null;
1618
1634
  if ((dir || shortcut) && lockedRef.current) return false;
@@ -1654,7 +1670,7 @@ function SwipeDeck(props) {
1654
1670
  const a2 = cardKey[d];
1655
1671
  if (a2?.shortcut?.toLowerCase() === k) return { answer: a2, dir: d };
1656
1672
  }
1657
- const a = cardMore.find((x) => x.shortcut?.toLowerCase() === k);
1673
+ const a = cardMoreShown.find((x) => x.shortcut?.toLowerCase() === k);
1658
1674
  return a ? { answer: a, dir: null } : null;
1659
1675
  };
1660
1676
  const exitRef = React4.useRef({ kind: props.exitKind, onExit: props.onExit });
@@ -1670,6 +1686,11 @@ function SwipeDeck(props) {
1670
1686
  if (layout === "inline" && !(root && (root.contains(active ?? null) || root.matches(":hover")))) return;
1671
1687
  const t = e.target instanceof Element ? e.target : null;
1672
1688
  if (t?.closest("input, textarea, select, [contenteditable='true']")) return;
1689
+ if ((e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey && (e.key === "z" || e.key === "Z") && !e.repeat) {
1690
+ e.preventDefault();
1691
+ undoRef.current();
1692
+ return;
1693
+ }
1673
1694
  if (e.altKey || e.ctrlKey || e.metaKey) return;
1674
1695
  if (e.repeat) {
1675
1696
  if (KEYS[e.key] || e.key === "z" || e.key === "Z" || e.key === "Backspace") e.preventDefault();
@@ -1826,7 +1847,7 @@ function SwipeDeck(props) {
1826
1847
  wasAtEnd.current = atEnd;
1827
1848
  }, [atEnd, result, onComplete, labels]);
1828
1849
  const copy = async () => {
1829
- const text2 = props.copyText ? props.copyText({ item: cardItem, state, result }) : cardItem ? itemCopyText(cardItem, state, cardKey, labels, conflict, cardMore) : deckCopyText(result, rules);
1850
+ const text2 = props.copyText ? props.copyText({ item: cardItem, state, result }) : cardItem ? itemCopyText(cardItem, state, cardKey, labels, conflict, cardMoreShown) : deckCopyText(result, rules);
1830
1851
  const ok = await copyToClipboard(text2);
1831
1852
  flash(ok ? labels.copied : labels.copyFailed);
1832
1853
  };
@@ -1880,11 +1901,41 @@ function SwipeDeck(props) {
1880
1901
  },
1881
1902
  children: [
1882
1903
  props.onExpand ? /* @__PURE__ */ jsx3(CornerButton, { corner: "tl", label: labels.expand, onClick: props.onExpand, children: /* @__PURE__ */ jsx3(Maximize2Icon, {}) }) : props.onExit ? /* @__PURE__ */ jsx3(CornerButton, { corner: "tl", label: props.exitKind === "collapse" ? labels.collapse : labels.exit, ...props.exitKind === "collapse" ? { shortcut: "Escape" } : {}, onClick: props.onExit, children: props.exitKind === "collapse" ? /* @__PURE__ */ jsx3(Minimize2Icon, {}) : /* @__PURE__ */ jsx3(XIcon, {}) }) : null,
1883
- /* @__PURE__ */ jsx3(CornerButton, { corner: "tr", label: labels.help, shortcut: "?", onClick: () => void tour(), disabled: !cardItem, children: /* @__PURE__ */ jsx3(CircleHelpIcon, {}) }),
1904
+ /* @__PURE__ */ jsxs3(CornerButton, { corner: "tr", label: labels.answered, onClick: () => setReviewing((v) => !v), disabled: !ready || !items.length || step.kind === "end", pressed: reviewing, children: [
1905
+ /* @__PURE__ */ jsx3(ListChecksIcon, {}),
1906
+ result.answered ? /* @__PURE__ */ jsx3("span", { className: "matrx-sd-corner-count", children: result.answered }) : null
1907
+ ] }),
1884
1908
  /* @__PURE__ */ jsx3(CornerButton, { corner: "bl", label: labels.undo, shortcut: "Z", onClick: undo, disabled: !controller.canUndo, children: /* @__PURE__ */ jsx3(Undo2Icon, {}) }),
1885
1909
  /* @__PURE__ */ jsx3(CornerButton, { corner: "br", label: labels.copy, onClick: () => void copy(), disabled: !ready || !items.length, children: /* @__PURE__ */ jsx3(CopyIcon, {}) }),
1886
1910
  cardItem ? /* @__PURE__ */ jsx3(EdgeButtons, { keyMap: cardKey, suggested: suggestedDir, refs: edgeRefs, onAnswer: (d) => act(d), suggestionLabel: labels.suggestion }) : null,
1887
- /* @__PURE__ */ jsx3("div", { className: "matrx-sd-stage", children: !ready ? /* @__PURE__ */ jsx3("div", { className: "matrx-sd-layer", "data-state": controller.status, children: controller.status === "error" ? /* @__PURE__ */ jsx3(ErrorBox, { className: "matrx-sd-message", message: controller.readError ?? "", operation: "Read the deck", origin: "design-system/SwipeDeck" }) : /* @__PURE__ */ jsx3(Loader2Icon, { className: "matrx-spin matrx-sd-spinner" }) }) : items.length === 0 ? /* @__PURE__ */ jsx3(EmptyView, { title: props.empty?.title ?? labels.emptyTitle, line: props.empty?.line, action: props.empty?.action, layerRef }, "empty") : step.kind === "card" ? /* @__PURE__ */ jsx3(
1911
+ /* @__PURE__ */ jsx3("div", { className: "matrx-sd-stage", children: !ready ? /* @__PURE__ */ jsx3("div", { className: "matrx-sd-layer", "data-state": controller.status, children: controller.status === "error" ? /* @__PURE__ */ jsx3(ErrorBox, { className: "matrx-sd-message", message: controller.readError ?? "", operation: "Read the deck", origin: "design-system/SwipeDeck" }) : /* @__PURE__ */ jsx3(Loader2Icon, { className: "matrx-spin matrx-sd-spinner" }) }) : items.length === 0 ? /* @__PURE__ */ jsx3(EmptyView, { title: props.empty?.title ?? labels.emptyTitle, line: props.empty?.line, action: props.empty?.action, layerRef }, "empty") : reviewing && step.kind !== "end" ? /* @__PURE__ */ jsx3(
1912
+ EndView,
1913
+ {
1914
+ result,
1915
+ rules,
1916
+ labels,
1917
+ groupLabels: props.groupLabels,
1918
+ layerRef,
1919
+ enterFrom,
1920
+ review: {
1921
+ onBack: () => setReviewing(false),
1922
+ onHelp: () => {
1923
+ setReviewing(false);
1924
+ void tour();
1925
+ }
1926
+ },
1927
+ onChange: (item) => {
1928
+ enterFrom.current = null;
1929
+ const now = stateRef.current;
1930
+ const ops = [{ kind: "answer", itemId: item.id, answer: null }];
1931
+ if (item.group && now.picks[item.group] === item.id) ops.push({ kind: "pick", group: item.group, itemId: null });
1932
+ controller.commit(makeChange(now, ops, items));
1933
+ setReviewing(false);
1934
+ setSaid(`${labels.change}: ${item.text}`);
1935
+ }
1936
+ },
1937
+ "review"
1938
+ ) : step.kind === "card" ? /* @__PURE__ */ jsx3(
1888
1939
  CardView,
1889
1940
  {
1890
1941
  item: step.item,
@@ -1893,7 +1944,7 @@ function SwipeDeck(props) {
1893
1944
  conflict,
1894
1945
  labels,
1895
1946
  suggestedDir,
1896
- more: cardMore,
1947
+ more: cardMoreShown,
1897
1948
  detail: detailNow,
1898
1949
  onDetail: (d) => setCardDetail({ id: step.item.id, detail: d }),
1899
1950
  body: cardContext && props.renderCardBody ? props.renderCardBody(cardContext) : null,
@@ -2237,7 +2288,7 @@ function groupOf(entry, graded) {
2237
2288
  if (graded && entry.right !== null) return entry.right ? "right" : "wrong";
2238
2289
  return entry.answer.label;
2239
2290
  }
2240
- function EndView({ result, rules, labels, groupLabels, layerRef, enterFrom, actions, onChange, children }) {
2291
+ function EndView({ result, rules, labels, groupLabels, layerRef, enterFrom, actions, onChange, children, review }) {
2241
2292
  const layer = React4.useRef(null);
2242
2293
  React4.useLayoutEffect(() => {
2243
2294
  layerRef.current = layer.current;
@@ -2257,11 +2308,11 @@ function EndView({ result, rules, labels, groupLabels, layerRef, enterFrom, acti
2257
2308
  const answered = result.entries.filter((e) => e.answer);
2258
2309
  const shown = filter ? answered.filter((e) => groupOf(e, graded) === filter) : answered;
2259
2310
  const pct = score && score.graded ? score.right / score.graded : 1;
2260
- const kicker = first ? groupLabels?.[first[0]] ?? first[0] : score ? labels.score : null;
2261
- const headline = first ? first[1].text : score ? `${score.right} / ${score.graded}` : labels.allDone;
2311
+ const kicker = review ? null : first ? groupLabels?.[first[0]] ?? first[0] : score ? labels.score : null;
2312
+ const headline = review ? labels.answered : first ? first[1].text : score ? `${score.right} / ${score.graded}` : labels.allDone;
2262
2313
  const sub = labels.answeredOf.replace("{answered}", String(result.answered)).replace("{total}", String(result.total));
2263
- return /* @__PURE__ */ jsx3("div", { ref: layer, className: "matrx-sd-layer matrx-sd-endlayer", children: /* @__PURE__ */ jsxs3("div", { className: "matrx-sd-end", "data-graded": graded ? "" : void 0, children: [
2264
- /* @__PURE__ */ jsxs3("svg", { className: "matrx-sd-mark", viewBox: "0 0 72 72", "aria-hidden": "true", "data-tone": score ? pct >= 0.5 ? "success" : "warning" : "success", children: [
2314
+ return /* @__PURE__ */ jsx3("div", { ref: layer, className: "matrx-sd-layer matrx-sd-endlayer", children: /* @__PURE__ */ jsxs3("div", { className: "matrx-sd-end", "data-graded": graded ? "" : void 0, "data-review": review ? "" : void 0, children: [
2315
+ review ? null : /* @__PURE__ */ jsxs3("svg", { className: "matrx-sd-mark", viewBox: "0 0 72 72", "aria-hidden": "true", "data-tone": score ? pct >= 0.5 ? "success" : "warning" : "success", children: [
2265
2316
  /* @__PURE__ */ jsx3("circle", { className: "matrx-sd-mark-track", cx: "36", cy: "36", r: "32" }),
2266
2317
  /* @__PURE__ */ jsx3("circle", { className: "matrx-sd-mark-ring", cx: "36", cy: "36", r: "32", pathLength: 100, style: { strokeDasharray: `${Math.round(pct * 100)} 100` } }),
2267
2318
  !score || pct === 1 ? /* @__PURE__ */ jsx3("path", { className: "matrx-sd-mark-check", d: "M23 37.5l9 9 17-19", pathLength: 100 }) : null
@@ -2269,11 +2320,11 @@ function EndView({ result, rules, labels, groupLabels, layerRef, enterFrom, acti
2269
2320
  kicker ? /* @__PURE__ */ jsx3("div", { className: "matrx-sd-kicker", children: kicker }) : null,
2270
2321
  /* @__PURE__ */ jsx3("h2", { className: "matrx-sd-end-hero", children: headline }),
2271
2322
  /* @__PURE__ */ jsx3("div", { className: "matrx-sd-end-sub", children: sub }),
2272
- score && first ? /* @__PURE__ */ jsxs3("div", { className: "matrx-sd-endline", children: [
2323
+ score && first && !review ? /* @__PURE__ */ jsxs3("div", { className: "matrx-sd-endline", children: [
2273
2324
  /* @__PURE__ */ jsx3("div", { className: "matrx-sd-label", children: labels.score }),
2274
2325
  /* @__PURE__ */ jsx3("div", { className: "matrx-sd-value", children: `${score.right} / ${score.graded}` })
2275
2326
  ] }) : null,
2276
- rest.map(([group, item]) => /* @__PURE__ */ jsxs3("div", { className: "matrx-sd-endline", children: [
2327
+ (review ? [] : rest).map(([group, item]) => /* @__PURE__ */ jsxs3("div", { className: "matrx-sd-endline", children: [
2277
2328
  /* @__PURE__ */ jsx3("div", { className: "matrx-sd-label", children: groupLabels?.[group] ?? group }),
2278
2329
  /* @__PURE__ */ jsx3("div", { className: "matrx-sd-value", children: item.text })
2279
2330
  ] }, group)),
@@ -2310,6 +2361,8 @@ function EndView({ result, rules, labels, groupLabels, layerRef, enterFrom, acti
2310
2361
  ] }) : null,
2311
2362
  shown.length ? /* @__PURE__ */ jsx3("ul", { className: "matrx-sd-review", "aria-label": labels.yourAnswers, children: shown.map((e) => {
2312
2363
  const rightIds = e.item.correct === void 0 ? [] : typeof e.item.correct === "string" ? [e.item.correct] : e.item.correct;
2364
+ const said = typeof e.detail?.value === "string" && e.detail.value.trim() ? e.detail.value : e.item.text;
2365
+ const answerWords = typeof e.detail?.label === "string" && e.detail.label ? e.detail.label : e.answer?.label;
2313
2366
  const rightWords = rightIds.map((id) => findAnswer(rules, e.item, id)?.label).filter(Boolean).join(", ");
2314
2367
  return /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsxs3(
2315
2368
  "button",
@@ -2318,14 +2371,14 @@ function EndView({ result, rules, labels, groupLabels, layerRef, enterFrom, acti
2318
2371
  className: "matrx-sd-review-row",
2319
2372
  onClick: () => onChange(e.item),
2320
2373
  "aria-label": [
2321
- `${labels.change}: ${e.item.text}`,
2322
- [e.answer?.label, e.right === true ? labels.right : e.right === false ? labels.wrong : null].filter(Boolean).join(", "),
2374
+ `${labels.change}: ${said}`,
2375
+ [answerWords, e.right === true ? labels.right : e.right === false ? labels.wrong : null].filter(Boolean).join(", "),
2323
2376
  e.right === false && rightWords ? `${labels.rightAnswer}: ${rightWords}` : null
2324
2377
  ].filter(Boolean).join(". "),
2325
2378
  children: [
2326
2379
  /* @__PURE__ */ jsxs3("span", { className: "matrx-sd-review-text", children: [
2327
2380
  e.item.kicker ? /* @__PURE__ */ jsx3("span", { className: "matrx-sd-label", children: e.item.kicker }) : null,
2328
- /* @__PURE__ */ jsx3("span", { className: "matrx-sd-review-item", children: e.item.text }),
2381
+ /* @__PURE__ */ jsx3("span", { className: "matrx-sd-review-item", children: said }),
2329
2382
  e.right === false && rightWords ? /* @__PURE__ */ jsxs3("span", { className: "matrx-sd-review-right", children: [
2330
2383
  labels.rightAnswer,
2331
2384
  ": ",
@@ -2334,14 +2387,23 @@ function EndView({ result, rules, labels, groupLabels, layerRef, enterFrom, acti
2334
2387
  ] }),
2335
2388
  /* @__PURE__ */ jsxs3("span", { className: "matrx-sd-review-answer", "data-tone": e.right === null ? e.answer?.tone : e.right ? "success" : "destructive", children: [
2336
2389
  e.right === true ? /* @__PURE__ */ jsx3(CheckIcon, {}) : e.right === false ? /* @__PURE__ */ jsx3(XIcon, {}) : null,
2337
- /* @__PURE__ */ jsx3("span", { children: e.answer?.label })
2390
+ /* @__PURE__ */ jsx3("span", { children: answerWords })
2338
2391
  ] }),
2339
2392
  /* @__PURE__ */ jsx3(PencilIcon, { className: "matrx-sd-review-change", "aria-hidden": "true" })
2340
2393
  ]
2341
2394
  }
2342
2395
  ) }, e.item.id);
2343
2396
  }) }) : null,
2344
- actions ? /* @__PURE__ */ jsx3("div", { className: "matrx-sd-end-actions", children: actions }) : null
2397
+ review ? /* @__PURE__ */ jsxs3("div", { className: "matrx-sd-end-actions", children: [
2398
+ /* @__PURE__ */ jsx3("button", { type: "button", className: "matrx-sd-review-back", onClick: review.onBack, autoFocus: true, children: labels.backToCards }),
2399
+ review.onHelp ? (
2400
+ // ui-exception: the walkthrough, from the answered list (the corner it had now opens this list)
2401
+ /* @__PURE__ */ jsxs3("button", { type: "button", className: "matrx-sd-legend-chip", onClick: review.onHelp, children: [
2402
+ /* @__PURE__ */ jsx3(CircleHelpIcon, { "aria-hidden": "true" }),
2403
+ /* @__PURE__ */ jsx3("span", { children: labels.help })
2404
+ ] })
2405
+ ) : null
2406
+ ] }) : actions ? /* @__PURE__ */ jsx3("div", { className: "matrx-sd-end-actions", children: actions }) : null
2345
2407
  ] }) });
2346
2408
  }
2347
2409
  function EmptyView({ title, line, action, layerRef }) {