@sincpro/web-ui 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/dist/components/activity/Activity.js +1 -1
  2. package/dist/components/activity/Messages.d.ts +4 -2
  3. package/dist/components/activity/Messages.js +4 -4
  4. package/dist/components/activity/Timeline.js +1 -1
  5. package/dist/components/assist/AssistMark.js +1 -1
  6. package/dist/components/assist/AssistSuggestion.js +1 -1
  7. package/dist/components/assist/ChangeReview.js +2 -2
  8. package/dist/components/calendar/RecordCalendar.js +4 -1
  9. package/dist/components/form/Sheet.js +5 -2
  10. package/dist/components/lines/LineItems.d.ts +1 -1
  11. package/dist/components/lines/LineItems.js +2 -2
  12. package/dist/components/listing/ListingBar.js +1 -1
  13. package/dist/components/listing/RecordCard.d.ts +4 -0
  14. package/dist/components/listing/RecordCard.js +4 -1
  15. package/dist/components/listing/Row.d.ts +7 -0
  16. package/dist/components/listing/Row.js +7 -1
  17. package/dist/components/listing/ViewSwitcher.d.ts +7 -2
  18. package/dist/components/listing/index.d.ts +1 -1
  19. package/dist/components/listing/set.d.ts +4 -1
  20. package/dist/components/record/Breadcrumb.js +5 -1
  21. package/dist/components/record/DirtyActions.d.ts +20 -6
  22. package/dist/components/record/DirtyActions.js +55 -4
  23. package/dist/components/record/RecordHeader.d.ts +1 -1
  24. package/dist/components/record/RecordHeader.js +32 -13
  25. package/dist/components/record/RecordSkeleton.js +1 -1
  26. package/dist/components/search/CriteriaBuilder.d.ts +1 -1
  27. package/dist/components/search/CriteriaBuilder.js +3 -3
  28. package/dist/components/search/FilterBuilder.d.ts +1 -18
  29. package/dist/components/search/FilterBuilder.js +4 -4
  30. package/dist/components/search/JsonEditor.js +5 -1
  31. package/dist/components/search/SearchBar.d.ts +1 -2
  32. package/dist/components/search/SearchBar.js +12 -3
  33. package/dist/components/search/criteria.d.ts +17 -1
  34. package/dist/components/search/index.d.ts +2 -1
  35. package/dist/components/search/index.js +1 -0
  36. package/dist/components/shell/Panes.d.ts +18 -0
  37. package/dist/components/shell/Panes.js +36 -0
  38. package/dist/components/shell/index.d.ts +1 -0
  39. package/dist/components/shell/index.js +2 -0
  40. package/dist/foundations/calendar.d.ts +3 -1
  41. package/dist/foundations/catalog.d.ts +27 -0
  42. package/dist/foundations/catalog.js +48 -0
  43. package/dist/foundations/copy.d.ts +40 -62
  44. package/dist/foundations/copy.es.js +10 -0
  45. package/dist/foundations/copy.js +25 -93
  46. package/dist/{primitives/field/context.d.ts → foundations/form.d.ts} +9 -0
  47. package/dist/{primitives/field/mode.js → foundations/form.js} +11 -0
  48. package/dist/foundations/grouping.d.ts +9 -3
  49. package/dist/foundations/index.d.ts +2 -0
  50. package/dist/foundations/index.js +5 -0
  51. package/dist/foundations/sticky.d.ts +4 -0
  52. package/dist/foundations/sticky.js +1 -0
  53. package/dist/primitives/button/Button.d.ts +12 -2
  54. package/dist/primitives/button/Button.js +12 -2
  55. package/dist/primitives/cell/CellStack.js +1 -1
  56. package/dist/primitives/choice/ComboBox.d.ts +1 -1
  57. package/dist/primitives/choice/ComboBox.js +4 -4
  58. package/dist/primitives/choice/RadioGroup.d.ts +1 -1
  59. package/dist/primitives/choice/RadioGroup.js +3 -3
  60. package/dist/primitives/choice/SearchField.js +1 -1
  61. package/dist/primitives/choice/SegmentedControl.d.ts +1 -1
  62. package/dist/primitives/choice/SegmentedControl.js +2 -2
  63. package/dist/primitives/choice/Slider.d.ts +1 -1
  64. package/dist/primitives/choice/Slider.js +2 -2
  65. package/dist/primitives/choice/Switch.d.ts +1 -1
  66. package/dist/primitives/choice/Switch.js +2 -2
  67. package/dist/primitives/date/Calendar.js +1 -1
  68. package/dist/primitives/date/DatePicker.d.ts +1 -1
  69. package/dist/primitives/date/DatePicker.js +2 -2
  70. package/dist/primitives/date/DateTime.d.ts +1 -1
  71. package/dist/primitives/date/DateTime.js +2 -2
  72. package/dist/primitives/field/Checks.d.ts +1 -1
  73. package/dist/primitives/field/Checks.js +4 -4
  74. package/dist/primitives/field/DropZone.d.ts +1 -1
  75. package/dist/primitives/field/DropZone.js +2 -2
  76. package/dist/primitives/field/Field.d.ts +10 -5
  77. package/dist/primitives/field/Field.js +10 -6
  78. package/dist/primitives/field/Suggest.d.ts +1 -1
  79. package/dist/primitives/field/Suggest.js +2 -2
  80. package/dist/primitives/field/index.d.ts +1 -3
  81. package/dist/primitives/field/index.js +4 -4
  82. package/dist/primitives/notice/Notice.js +3 -1
  83. package/dist/primitives/picker/Picker.d.ts +1 -1
  84. package/dist/primitives/picker/Picker.js +4 -4
  85. package/dist/primitives/{tag → picker}/TagsField.d.ts +2 -2
  86. package/dist/primitives/{tag → picker}/TagsField.js +5 -5
  87. package/dist/primitives/picker/index.d.ts +2 -0
  88. package/dist/primitives/picker/index.js +5 -1
  89. package/dist/primitives/table/CellCopy.js +1 -1
  90. package/dist/primitives/table/CellEditor.js +2 -4
  91. package/dist/primitives/table/DataTable.d.ts +11 -1
  92. package/dist/primitives/table/DataTable.js +111 -38
  93. package/dist/primitives/table/EditableTable.js +6 -2
  94. package/dist/primitives/table/PivotTable.js +0 -0
  95. package/dist/primitives/table/ReportTable.js +4 -3
  96. package/dist/primitives/table/SheetTable.js +8 -14
  97. package/dist/primitives/table/Table.js +2 -2
  98. package/dist/primitives/tabs/Tabs.d.ts +22 -4
  99. package/dist/primitives/tabs/Tabs.js +77 -23
  100. package/dist/primitives/tabs/index.d.ts +1 -1
  101. package/dist/primitives/tabs/index.js +1 -1
  102. package/dist/primitives/tag/Tags.js +4 -1
  103. package/dist/primitives/tag/index.d.ts +0 -2
  104. package/dist/primitives/tag/index.js +0 -3
  105. package/dist/primitives/value/Avatar.js +8 -1
  106. package/dist/primitives/value/{Fecha.d.ts → DateValue.d.ts} +5 -2
  107. package/dist/primitives/value/{Fecha.js → DateValue.js} +9 -8
  108. package/dist/primitives/value/Relation.d.ts +1 -1
  109. package/dist/primitives/value/Relation.js +2 -2
  110. package/dist/primitives/value/Value.js +4 -1
  111. package/dist/primitives/value/index.d.ts +1 -1
  112. package/dist/primitives/value/index.js +1 -1
  113. package/dist/tokens.css +42 -0
  114. package/dist/views/list/List.d.ts +22 -2
  115. package/dist/views/list/List.js +28 -6
  116. package/dist/views/list/ListView.d.ts +1 -1
  117. package/dist/views/list/ListView.js +47 -16
  118. package/dist/views/list/index.d.ts +2 -2
  119. package/dist/views/list/presentations.js +3 -1
  120. package/dist/views/list/types.d.ts +57 -1
  121. package/dist/views/list/useNarrowList.d.ts +2 -2
  122. package/dist/views/list/useNarrowList.js +5 -5
  123. package/dist/views/record/Record.js +46 -42
  124. package/package.json +1 -1
  125. package/dist/primitives/field/context.js +0 -13
  126. package/dist/primitives/field/mode.d.ts +0 -9
  127. /package/dist/primitives/field/{copy.d.ts → clipboard.d.ts} +0 -0
  128. /package/dist/primitives/field/{copy.js → clipboard.js} +0 -0
  129. /package/dist/primitives/{tag/choices.d.ts → picker/tagChoices.d.ts} +0 -0
  130. /package/dist/primitives/{tag/choices.js → picker/tagChoices.js} +0 -0
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useUiCopy } from "../../foundations/copy.js";
3
+ import { fillCopy, useUiCopy } from "../../foundations/copy.js";
4
4
  import { stickyTop, useScrollBox } from "../../foundations/sticky.js";
5
5
  import { Cell } from "../value/Value.js";
6
6
  import { useVirtualizer } from "@tanstack/react-virtual";
@@ -168,7 +168,7 @@ note, height = 420, rowLabel, band, onBandToggle, caption, onCommit, editable, o
168
168
  }
169
169
  }
170
170
  }
171
- return (_jsxs("div", { className: "flex min-w-0 flex-col", children: [caption === undefined ? null : (_jsx("div", { className: "px-3 pb-2 text-meta text-muted", children: caption })), _jsx("div", { ref: caja.ref, style: caja.style, onKeyDown: isEditable ? onKey : undefined, tabIndex: isEditable ? 0 : undefined, className: `relative min-w-0 rounded-box border border-line bg-surface focus:outline-none ${caja.className}`, role: "grid", "aria-rowcount": rows, "aria-colcount": columns.length + 1, children: _jsxs("div", { style: {
171
+ return (_jsxs("div", { className: "flex min-w-0 flex-col", children: [caption === undefined ? null : (_jsx("div", { className: "px-3 pb-2 text-meta text-muted", children: caption })), _jsx("div", { ref: caja.ref, style: caja.style, onKeyDown: isEditable ? onKey : undefined, tabIndex: isEditable ? 0 : caja.tabIndex, className: `relative min-w-0 rounded-box border border-line bg-surface outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary ${caja.className}`, role: "grid", "aria-rowcount": rows, "aria-colcount": columns.length + 1, children: _jsxs("div", { style: {
172
172
  minWidth: `${ROW_LABEL_WIDTH + columns.reduce((a, c) => a + (c.width ?? WIDTH), 0)}px`,
173
173
  }, children: [_jsxs("div", { className: "sticky z-20 flex border-b border-line-strong bg-ground", style: stickyTop, role: "row", children: [_jsx("div", { className: "sticky left-0 z-30 shrink-0 border-r border-line bg-ground", style: { width: ROW_LABEL_WIDTH } }), columns.map((c) => (_jsxs("div", { role: "columnheader", style: { width: c.width ?? WIDTH }, className: `flex shrink-0 flex-col justify-center gap-0.5 border-r border-line px-2.5 py-1.5 ${c.numeric === true ? "items-end" : "items-start"}`, children: [_jsx("span", { className: "max-w-full truncate text-meta font-medium text-ink", title: c.header, children: c.header }), c.type === undefined ? null : (_jsx("span", { className: "max-w-full truncate font-mono text-micro text-muted", children: c.type }))] }, c.id))), _jsx("div", { className: "flex-1" })] }), _jsx("div", { style: { height: v.getTotalSize(), position: "relative" }, children: items.map((item) => {
174
174
  const montón = band?.(item.index);
@@ -183,21 +183,15 @@ note, height = 420, rowLabel, band, onBandToggle, caption, onCommit, editable, o
183
183
  que esa columna sume algo—. Antes era un rótulo suelto que
184
184
  ignoraba las columnas, y entonces un total quedaba en un
185
185
  rincón sin decir de qué columna era. */
186
- _jsxs("div", { role: "row", "aria-rowindex": item.index + 1, className: "absolute top-0 left-0 flex w-full border-b border-line-strong bg-ground", style: { height: item.size, transform: `translateY(${item.start}px)` }, children: [_jsx("button", { type: "button", onClick: onBandToggle === undefined
187
- ? undefined
188
- : () => onBandToggle(item.index), "aria-expanded": montón.open, className: "sticky left-0 z-10 flex min-w-0 shrink-0 items-center gap-1.5 border-r border-line bg-ground px-2.5 text-left", style: {
189
- height: "100%",
190
- /* El mismo ancho que la columna del número de fila, para
191
- que el rótulo no corra la cuadrícula. */
192
- width: ROW_LABEL_WIDTH,
193
- ...(montón.depth === undefined || montón.depth === 0
194
- ? {}
186
+ _jsxs("div", { role: "row", "aria-rowindex": item.index + 1, className: "absolute top-0 left-0 flex w-full border-b border-line-strong bg-ground", style: { height: item.size, transform: `translateY(${item.start}px)` }, children: [_jsx("div", { role: "gridcell", className: "sticky left-0 z-10 shrink-0 border-r border-line bg-ground", style: { height: "100%", width: ROW_LABEL_WIDTH }, children: onBandToggle === undefined ? null : (_jsx("button", { type: "button", onClick: () => onBandToggle(item.index), "aria-expanded": montón.open, "aria-label": fillCopy(montón.open ? copy.report.close : copy.report.open, {
187
+ name: montón.label,
188
+ }), className: "flex h-full w-full min-w-0 items-center gap-1.5 px-2.5 text-left focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary", style: montón.depth === undefined || montón.depth === 0
189
+ ? undefined
195
190
  : {
196
191
  paddingLeft: `calc(var(--spacing-indent) * ${montón.depth} + 0.625rem)`,
197
- }),
198
- }, children: onBandToggle === undefined ? null : (_jsx(ChevronRight, { className: `size-(--size-icon-sm) shrink-0 text-muted transition-transform duration-(--duration-instant) ${montón.open ? "rotate-90" : ""}`, strokeWidth: 2, "aria-hidden": true })) }), columns.map((c, ci) => {
192
+ }, children: _jsx(ChevronRight, { className: `size-(--size-icon-sm) shrink-0 text-muted transition-transform duration-(--duration-instant) ${montón.open ? "rotate-90" : ""}`, strokeWidth: 2, "aria-hidden": true }) })) }), columns.map((c, ci) => {
199
193
  const total = montón.total?.(c, ci) ?? null;
200
- return (_jsx("div", { role: "gridcell", style: { width: c.width ?? WIDTH }, className: `flex shrink-0 items-center overflow-hidden border-r border-line px-2.5 whitespace-nowrap ${c.numeric === true ? "justify-end" : "justify-start"}`, children: ci === 0 ? (_jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [montón.field === undefined ? null : (_jsx("span", { className: "shrink-0 text-micro text-muted/70", children: montón.field })), _jsx("span", { className: "truncate text-micro font-medium text-ink", children: montón.label }), _jsx("span", { className: "inline-flex shrink-0 items-center rounded-pill border border-line-strong bg-ink/5 px-2 py-0.5 text-micro tabular-nums text-muted", children: montón.count.toLocaleString(copy.locale) })] })) : total === null ? null : (_jsx("span", { className: "truncate text-meta font-medium tabular-nums text-ink", children: typeof total === "number"
194
+ return (_jsx("div", { role: "gridcell", style: { width: c.width ?? WIDTH }, className: `flex shrink-0 items-center overflow-hidden border-r border-line px-2.5 whitespace-nowrap ${c.numeric === true ? "justify-end" : "justify-start"}`, children: ci === 0 ? (_jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [montón.field === undefined ? null : (_jsx("span", { className: "shrink-0 text-micro text-muted", children: montón.field })), _jsx("span", { className: "truncate text-micro font-medium text-ink", children: montón.label }), _jsx("span", { className: "inline-flex shrink-0 items-center rounded-pill border border-line-strong bg-ink/5 px-2 py-0.5 text-micro tabular-nums text-muted", children: montón.count.toLocaleString(copy.locale) })] })) : total === null ? null : (_jsx("span", { className: "truncate text-meta font-medium tabular-nums text-ink", children: typeof total === "number"
201
195
  ? total.toLocaleString(copy.locale)
202
196
  : total })) }, c.id));
203
197
  })] }, item.key));
@@ -13,7 +13,7 @@ height, rowCount, }) {
13
13
  `useScrollBox`: ahí vive el dilema entero y ahí vive su explicación. Las
14
14
  tres tablas del sistema lo resolvían con tres copias de la misma
15
15
  medición. */
16
- _jsx("div", { ref: caja.ref, className: `min-w-0 ${caja.className}`, style: caja.style, children: _jsx("table", { "aria-rowcount": rowCount, className: "w-full border-separate border-spacing-0 px-1 text-left", children: children }) }));
16
+ _jsx("div", { ref: caja.ref, tabIndex: caja.tabIndex, className: `min-w-0 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary ${caja.className}`, style: caja.style, children: _jsx("table", { "aria-rowcount": rowCount, className: "w-full border-separate border-spacing-0 px-1 text-left", children: children }) }));
17
17
  }
18
18
  /** The text of a column header, shared by every table of the family. Ink and semibold, not
19
19
  * muted: in grey at the cells' weight the names of the columns read as one more row. */
@@ -110,7 +110,7 @@ mono = false,
110
110
  * gets lost. */
111
111
  tone, title, }) {
112
112
  const mark = useContext(RowMark);
113
- return (_jsx("td", { title: title, className: `border-b border-line px-3 py-1.5 text-meta text-ink first:rounded-l-control last:rounded-r-control ${(align ?? (numeric ? "right" : "left")) === "right" ? "text-right" : "text-left"} ${mono ? "font-mono tabular-nums" : numeric ? "tabular-nums" : ""} ${tone === undefined ? rowWash(mark) : CELL_TONE[tone]}`, children: children }));
113
+ return (_jsx("td", { title: title, className: `border-b border-line px-3 py-1.5 text-meta text-ink first:rounded-l-control last:rounded-r-control ${(align ?? (numeric ? "right" : "left")) === "right" ? "text-right" : "text-left"} ${mono ? "font-mono tabular-nums" : numeric ? "tabular-nums whitespace-nowrap" : ""} ${tone === undefined ? rowWash(mark) : CELL_TONE[tone]}`, children: children }));
114
114
  }
115
115
  /** The selection checkbox, with its third state.
116
116
  *
@@ -1,5 +1,6 @@
1
1
  import type { Tone } from "../../foundations/types.js";
2
2
  import type { LucideIcon } from "lucide-react";
3
+ import { type ReactNode } from "react";
3
4
  /**
4
5
  * UNA TIRA DE PESTAÑAS. La misma en todo el sistema.
5
6
  *
@@ -76,11 +77,28 @@ export type TabItem = {
76
77
  notice?: TabNotice;
77
78
  };
78
79
  /**
79
- * The strip only: what each tab opens is drawn by whoever uses it (`Record`, `Activity`).
80
+ * A notebook whose strip and page are far apart — the record's tabs ride in its header and
81
+ * the page is drawn in its paper. It only shares an id: no box, no context of anything
82
+ * else, so it can wrap a whole record without touching what is inside.
80
83
  *
81
- * Built on React Aria's `Tabs`, so the strip is a real tab list for the keyboard: the
82
- * arrows move between tabs and select them, Home/End jump to the ends, and only the open
83
- * tab is in the Tab order. The hand-written version had the roles and none of the keys.
84
+ * Context: it was React Aria's `Tabs`, and that one walks its whole subtree to collect its
85
+ * items — wrapped around a record it collected the breadcrumbs too, and broke them.
86
+ */
87
+ export declare function TabsScope({ children }: {
88
+ children: ReactNode;
89
+ }): import("react").JSX.Element;
90
+ /** The page of the open tab, inside a `TabsScope`. `id` is the tab's. */
91
+ export declare function TabsPanel({ id, className, children, }: {
92
+ id: string;
93
+ className?: string;
94
+ children: ReactNode;
95
+ }): import("react").JSX.Element;
96
+ /**
97
+ * The strip. Inside a `TabsScope` each tab points at its panel; alone it is a strip whose
98
+ * page the caller draws.
99
+ *
100
+ * The keyboard is the ARIA tabs pattern with automatic activation: one tab stop (the open
101
+ * tab), ← / → move AND open (wrapping), Home / End go to the ends.
84
102
  */
85
103
  export declare function Tabs({ tabs, current, onSelect, label, }: {
86
104
  tabs: readonly TabItem[];
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useUiCopy } from "../../foundations/copy.js";
4
- import { Tab, TabList, Tabs as AriaTabs } from "react-aria-components";
4
+ import { createContext, useContext, useId, useRef, } from "react";
5
5
  /* El color de la notificación. Estático y en una tabla porque Tailwind no puede
6
6
  componer una clase con un pedazo variable: `bg-${tone}/12` no existe en la
7
7
  hoja. */
@@ -28,29 +28,83 @@ const NOTICE_DOT = {
28
28
  * arrows move between tabs and select them, Home/End jump to the ends, and only the open
29
29
  * tab is in the Tab order. The hand-written version had the roles and none of the keys.
30
30
  */
31
+ /* Set by `TabsScope`: the strip and its page share an id, so each tab can say which panel
32
+ it controls and the panel which tab names it. */
33
+ const Scope = createContext(null);
34
+ const tabIdOf = (base, id) => `${base}-tab-${id}`;
35
+ const panelIdOf = (base, id) => `${base}-panel-${id}`;
36
+ /**
37
+ * A notebook whose strip and page are far apart — the record's tabs ride in its header and
38
+ * the page is drawn in its paper. It only shares an id: no box, no context of anything
39
+ * else, so it can wrap a whole record without touching what is inside.
40
+ *
41
+ * Context: it was React Aria's `Tabs`, and that one walks its whole subtree to collect its
42
+ * items — wrapped around a record it collected the breadcrumbs too, and broke them.
43
+ */
44
+ export function TabsScope({ children }) {
45
+ return _jsx(Scope, { value: useId(), children: children });
46
+ }
47
+ /** The page of the open tab, inside a `TabsScope`. `id` is the tab's. */
48
+ export function TabsPanel({ id, className, children, }) {
49
+ const base = useContext(Scope);
50
+ return (_jsx("div", { role: "tabpanel", ...(base === null
51
+ ? {}
52
+ : { id: panelIdOf(base, id), "aria-labelledby": tabIdOf(base, id) }),
53
+ /* Reachable on its own (the ARIA pattern), with the ring every focus stop has. */
54
+ tabIndex: 0, className: `focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary ${className ?? ""}`, children: children }));
55
+ }
56
+ /**
57
+ * The strip. Inside a `TabsScope` each tab points at its panel; alone it is a strip whose
58
+ * page the caller draws.
59
+ *
60
+ * The keyboard is the ARIA tabs pattern with automatic activation: one tab stop (the open
61
+ * tab), ← / → move AND open (wrapping), Home / End go to the ends.
62
+ */
31
63
  export function Tabs({ tabs, current, onSelect, label, }) {
32
64
  const copy = useUiCopy();
33
- return (_jsx(AriaTabs, { selectedKey: current, onSelectionChange: (key) => onSelect?.(String(key)), children: _jsx(TabList, { "aria-label": label ?? copy.record.faces, className: "flex min-w-0 flex-wrap items-end gap-1", children: tabs.map((a) => {
34
- const Icon = a.icon;
35
- const empty = a.count === 0;
36
- /* Un cero no pide nada, así que no se dibuja. Es la diferencia con el
37
- contador, donde «0» es una respuesta. */
38
- const notice = a.notice?.count === 0 ? undefined : a.notice;
39
- return (_jsx(Tab, { id: a.id, ...(notice === undefined
40
- ? {}
41
- : { "aria-label": `${a.label} · ${notice.label}` }), className: ({ isSelected }) => `inline-flex shrink-0 cursor-default items-center px-3 py-2 text-meta outline-0 transition-colors duration-(--duration-instant) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary ${isSelected
42
- ? "-mb-px rounded-t-control border border-b-0 border-line-strong bg-surface font-medium text-ink"
43
- : "border border-transparent text-muted hover:text-ink"}`, children: ({ isSelected }) => (
44
- /* The `title` goes on the content and not on the tab: React Aria does not
45
- forward it, and on the content the native tooltip still covers the
46
- whole tab. */
47
- _jsxs("span", { className: "inline-flex items-center gap-1.5", ...(notice === undefined ? {} : { title: notice.label }), children: [Icon === undefined ? null : (_jsx(Icon, { className: `size-(--size-icon-sm) shrink-0 ${isSelected ? "text-ink" : "text-muted"}`, strokeWidth: 2, "aria-hidden": true })), a.label, a.count === undefined ? null : (_jsx("span", { className: `rounded-pill px-1.5 text-micro tabular-nums ${empty
48
- ? "bg-ink/5 text-muted/70"
49
- : isSelected
50
- ? "bg-ink/8 text-ink"
51
- : "bg-ink/8 text-muted"}`, children: a.count.toLocaleString(copy.locale) })), notice === undefined ? null : notice.count === undefined ? (
52
- /* Sin número, un punto. La palabra no se dibuja pero está: en el
65
+ const base = useContext(Scope);
66
+ const strip = useRef(null);
67
+ const go = (to) => {
68
+ const next = tabs[(to + tabs.length) % tabs.length];
69
+ if (next === undefined)
70
+ return;
71
+ onSelect?.(next.id);
72
+ strip.current?.querySelector(`[data-tab="${CSS.escape(next.id)}"]`)?.focus();
73
+ };
74
+ const onKeyDown = (event) => {
75
+ const at = tabs.findIndex((one) => one.id === current);
76
+ const to = event.key === "ArrowRight"
77
+ ? at + 1
78
+ : event.key === "ArrowLeft"
79
+ ? at - 1
80
+ : event.key === "Home"
81
+ ? 0
82
+ : event.key === "End"
83
+ ? tabs.length - 1
84
+ : null;
85
+ if (to === null)
86
+ return;
87
+ event.preventDefault();
88
+ go(to);
89
+ };
90
+ return (_jsx("div", { ref: strip, role: "tablist", "aria-label": label ?? copy.record.faces, onKeyDown: onKeyDown, className: "flex min-w-0 flex-wrap items-end gap-1", children: tabs.map((a) => {
91
+ const Icon = a.icon;
92
+ const empty = a.count === 0;
93
+ /* Un cero no pide nada, así que no se dibuja. Es la diferencia con el
94
+ contador, donde «0» es una respuesta. */
95
+ const notice = a.notice?.count === 0 ? undefined : a.notice;
96
+ const isSelected = a.id === current;
97
+ return (_jsx("button", { type: "button", role: "tab", "data-tab": a.id, "aria-selected": isSelected, tabIndex: isSelected ? 0 : -1, ...(base === null
98
+ ? {}
99
+ : { id: tabIdOf(base, a.id), "aria-controls": panelIdOf(base, a.id) }), onClick: () => onSelect?.(a.id), className: `inline-flex shrink-0 cursor-default items-center px-3 py-2 text-meta outline-0 transition-colors duration-(--duration-instant) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary ${isSelected
100
+ ? "-mb-px rounded-t-control border border-b-0 border-line-strong bg-surface font-medium text-ink"
101
+ : "border border-transparent text-muted hover:text-ink"}`, children: _jsxs("span", { className: "inline-flex items-center gap-1.5", ...(notice === undefined ? {} : { title: notice.label }), children: [Icon === undefined ? null : (_jsx(Icon, { className: `size-(--size-icon-sm) shrink-0 ${isSelected ? "text-ink" : "text-muted"}`, strokeWidth: 2, "aria-hidden": true })), a.label, a.count === undefined ? null : (_jsx("span", { className: `rounded-pill px-1.5 text-micro tabular-nums ${empty
102
+ ? "bg-ink/5 text-muted"
103
+ : isSelected
104
+ ? "bg-ink/8 text-ink"
105
+ : "bg-ink/8 text-muted"}`, children: a.count.toLocaleString(copy.locale) })), notice === undefined ? null : (_jsx("span", { className: "sr-only", children: ` · ${notice.label}` })), notice === undefined ? null : notice.count === undefined ? (
106
+ /* Sin número, un punto. La palabra no se dibuja pero está: en el
53
107
  `title` y en el nombre accesible de la pestaña. */
54
- _jsx("span", { className: `size-1.5 shrink-0 rounded-pill ${NOTICE_DOT[notice.tone ?? "bad"]}`, "aria-hidden": true })) : (_jsx("span", { className: `rounded-pill px-1.5 text-micro font-medium tabular-nums ${NOTICE_TONE[notice.tone ?? "bad"]}`, "aria-hidden": true, children: notice.count.toLocaleString(copy.locale) }))] })) }, a.id));
55
- }) }) }));
108
+ _jsx("span", { className: `size-1.5 shrink-0 rounded-pill ${NOTICE_DOT[notice.tone ?? "bad"]}`, "aria-hidden": true })) : (_jsx("span", { className: `rounded-pill px-1.5 text-micro font-medium tabular-nums ${NOTICE_TONE[notice.tone ?? "bad"]}`, "aria-hidden": true, children: notice.count.toLocaleString(copy.locale) }))] }) }, a.id));
109
+ }) }));
56
110
  }
@@ -1 +1 @@
1
- export { type TabItem, type TabNotice, Tabs } from "./Tabs.js";
1
+ export { type TabItem, type TabNotice, Tabs, TabsPanel, TabsScope, } from "./Tabs.js";
@@ -1 +1 @@
1
- export { Tabs } from "./Tabs.js";
1
+ export { Tabs, TabsPanel, TabsScope, } from "./Tabs.js";
@@ -96,7 +96,10 @@ function shell({ item, surface, stack, removable, pressable, }) {
96
96
  }
97
97
  function Inside({ item }) {
98
98
  const Icon = item.icon;
99
- return (_jsxs(_Fragment, { children: [Icon === undefined ? null : (_jsx(Icon, { className: "size-3 shrink-0 opacity-70", strokeWidth: 2, "aria-hidden": true })), item.avatar === undefined ? null : (_jsx("span", { "aria-hidden": true, className: "-my-0.5 -ml-1.5 flex shrink-0", children: _jsx(Avatar, { name: item.label, size: "sm", ...(typeof item.avatar === "string" ? { src: item.avatar } : {}) }) })), _jsx("span", { className: "min-w-0 truncate", children: item.label }), item.note === undefined ? null : (_jsxs("span", { className: "shrink-0 opacity-60", children: ["\u00B7 ", item.note] }))] }));
99
+ return (_jsxs(_Fragment, { children: [Icon === undefined ? null : (_jsx(Icon, { className: "size-3 shrink-0 opacity-70", strokeWidth: 2, "aria-hidden": true })), item.avatar === undefined ? null : (_jsx("span", { "aria-hidden": true, className: "-my-0.5 -ml-1.5 flex shrink-0", children: _jsx(Avatar, { name: item.label, size: "sm", ...(typeof item.avatar === "string" ? { src: item.avatar } : {}) }) })), _jsx("span", { className: "min-w-0 truncate", children: item.label }), item.note === undefined ? null : (
100
+ /* Secondary by its separator and weight, not by fading: at 60 % the note fell
101
+ under 4.5:1 on a tinted chip. */
102
+ _jsxs("span", { className: "shrink-0 font-normal", children: ["\u00B7 ", item.note] }))] }));
100
103
  }
101
104
  /* The × is React Aria's `remove` slot: it removes THIS chip and never presses it, so
102
105
  removing a pressable chip does not also choose it. */
@@ -1,4 +1,2 @@
1
1
  export { StatusDot, Tag } from "./Tag.js";
2
2
  export { type TagItem, Tags } from "./Tags.js";
3
- export { type TagOption } from "./choices.js";
4
- export { TagsField, type TagsFieldProps } from "./TagsField.js";
@@ -3,6 +3,3 @@ export { StatusDot, Tag } from "./Tag.js";
3
3
  /* La tira de fichas: la misma en la barra, en el desplegable y en cualquier
4
4
  lugar donde se dibuje una lista de palabras cortas. */
5
5
  export { Tags } from "./Tags.js";
6
- /* El many2many: las fichas elegidas más una entrada que agrega la siguiente. */
7
- export {} from "./choices.js";
8
- export { TagsField } from "./TagsField.js";
@@ -87,5 +87,12 @@ src, }) {
87
87
  the same baseline the initials disc has. */
88
88
  _jsxs("span", { title: name, className: `relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-pill ${SIZE[size]}`, children: [_jsx("span", { "aria-hidden": true, className: "invisible", children: initials(name) }), _jsx("img", { src: src, alt: name, className: "absolute inset-0 size-full object-cover" })] }));
89
89
  }
90
- return (_jsx("span", { title: name, "aria-hidden": true, className: `inline-flex shrink-0 items-center justify-center rounded-pill font-medium select-none ${SIZE[size]} ${toneOf(name, surface)}`, children: initials(name) }));
90
+ /* Each identity is a tint and an ink: the tint goes on the disc, the ink on the letters. */
91
+ const [tint = "", ink = ""] = toneOf(name, surface).split(" ");
92
+ return (
93
+ /* The tint lies over an OPAQUE disc of the surface. Laid straight over whatever is
94
+ behind, a translucent tint took that colour: on a calendar event the letters fell to
95
+ 3.8:1. Over the surface it is the same disc everywhere, and the contrast is the one
96
+ measured for it. */
97
+ _jsxs("span", { title: name, "aria-hidden": true, className: `relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-pill font-medium select-none ${SIZE[size]} ${surface === "chrome" ? "bg-chrome" : "bg-surface"}`, children: [_jsx("span", { className: `absolute inset-0 ${tint}` }), _jsx("span", { className: `relative ${ink}`, children: initials(name) })] }));
91
98
  }
@@ -1,5 +1,8 @@
1
- export declare function formatRelative(when: Date, now?: Date): string;
2
- export declare function formatDateTime(when: Date): string;
1
+ import { type UiCopy } from "../../foundations/copy.js";
2
+ /** Context: `copy` defaults to the installed words — what a caller outside a component has;
3
+ * a component passes its branch's (`useUiCopy()`), so a provider reaches the sentence. */
4
+ export declare function formatRelative(when: Date, now?: Date, copy?: UiCopy): string;
5
+ export declare function formatDateTime(when: Date, locale?: string): string;
3
6
  export declare function DateValue({ when, format,
4
7
  /** So it can be tested: without this, a story with «hace 2 min» changes its
5
8
  * text every minute and no snapshot works as a reference. */
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { currentCopy, currentCopyLocale, useUiCopy } from "../../foundations/copy.js";
3
+ import { currentCopy, currentCopyLocale, useUiCopy, } from "../../foundations/copy.js";
4
4
  /* ==================================================================== *
5
5
  * WHEN — a date says two things and they can't both be said together.
6
6
  *
@@ -45,20 +45,21 @@ const STEPS = [
45
45
  [HOUR, "hour"],
46
46
  [DAY, "day"],
47
47
  ];
48
- export function formatRelative(when, now = new Date()) {
49
- const locale = currentCopyLocale();
48
+ /** Context: `copy` defaults to the installed words — what a caller outside a component has;
49
+ * a component passes its branch's (`useUiCopy()`), so a provider reaches the sentence. */
50
+ export function formatRelative(when, now = new Date(), copy = currentCopy()) {
51
+ const locale = copy.locale;
50
52
  const delta = when.getTime() - now.getTime();
51
53
  const abs = Math.abs(delta);
52
54
  if (abs < MINUTE)
53
- return currentCopy().time.justNow;
55
+ return copy.time.justNow;
54
56
  if (abs > 7 * DAY)
55
57
  return dateFormat(locale).format(when);
56
58
  const step = STEPS.find(([ms]) => abs < ms * 60) ?? STEPS[STEPS.length - 1];
57
59
  const [ms, unit] = step;
58
60
  return relativeFormat(locale).format(Math.round(delta / ms), unit);
59
61
  }
60
- export function formatDateTime(when) {
61
- const locale = currentCopyLocale();
62
+ export function formatDateTime(when, locale = currentCopyLocale()) {
62
63
  return `${dateFormat(locale).format(when)} ${timeFormat(locale).format(when)}`;
63
64
  }
64
65
  /** A day written as `YYYY-MM-DD` (what a date field stores) is that day HERE. `new Date()`
@@ -79,11 +80,11 @@ now, }) {
79
80
  if (Number.isNaN(d.getTime())) {
80
81
  return (_jsx("span", { className: "text-meta text-muted italic", title: copy.time.invalid, children: copy.time.noDate }));
81
82
  }
82
- const exact = formatDateTime(d);
83
+ const exact = formatDateTime(d, copy.locale);
83
84
  const text = format === "exact"
84
85
  ? exact
85
86
  : format === "date"
86
87
  ? dateFormat(copy.locale).format(d)
87
- : formatRelative(d, now);
88
+ : formatRelative(d, now, copy);
88
89
  return (_jsx("time", { dateTime: d.toISOString(), title: exact, className: "text-meta whitespace-nowrap tabular-nums text-muted", children: text }));
89
90
  }
@@ -1,5 +1,5 @@
1
+ import { type FormMode } from "../../foundations/form.js";
1
2
  import type { OpenTarget, Tone } from "../../foundations/types.js";
2
- import { type FormMode } from "../field/mode.js";
3
3
  import { type PersonContact } from "./contact.js";
4
4
  import type { LucideIcon } from "lucide-react";
5
5
  import { type ReactNode } from "react";
@@ -1,11 +1,11 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { fillCopy, useUiCopy } from "../../foundations/copy.js";
4
+ import { FieldIdsContext } from "../../foundations/form.js";
5
+ import { useFormMode } from "../../foundations/form.js";
4
6
  import { cn, focusRing } from "../../foundations/variants.js";
5
7
  import { Button, ButtonLink, IconButton } from "../button/Button.js";
6
8
  import { CellStack, Thumb } from "../cell/CellStack.js";
7
- import { FieldIdsContext } from "../field/context.js";
8
- import { useFormMode } from "../field/mode.js";
9
9
  import { Menu } from "../menu/Menu.js";
10
10
  import { PopoverTrigger } from "../overlay/PopoverTrigger.js";
11
11
  import { Preview } from "../overlay/Preview.js";
@@ -64,7 +64,10 @@ export function Cell({ value,
64
64
  max = 48, }) {
65
65
  const copy = useUiCopy();
66
66
  if (value === null || value === undefined) {
67
- return (_jsx("span", { className: "text-muted italic", title: copy.cell.missing, children: copy.cell.missing }));
67
+ return (
68
+ /* Said by the word and the italic, in ink: muted, it fell under 4.5:1 on a toned
69
+ cell — and a missing value is exactly what someone has to see. */
70
+ _jsx("span", { className: "text-ink italic", title: copy.cell.missing, children: copy.cell.missing }));
68
71
  }
69
72
  if (typeof value === "number")
70
73
  return _jsx(Numeric, { value: value, format: "decimal" });
@@ -1,5 +1,5 @@
1
1
  export { Copyable, type CopyableProps, CopyButton, } from "./Copyable.js";
2
- export { DateValue, formatDateTime, formatRelative, } from "./Fecha.js";
2
+ export { DateValue, formatDateTime, formatRelative, } from "./DateValue.js";
3
3
  export { Meter, type MeterLevel, meterLevel, type MeterProps, type MeterThresholds, } from "./Meter.js";
4
4
  export { Metric, type MetricProps, Metrics } from "./Metric.js";
5
5
  export { Avatar, type AvatarSize, type PersonContact, UserChip, type UserChipProps, UserStack, } from "./Person.js";
@@ -1,6 +1,6 @@
1
1
  /* Show a number or a datum so that it reads as such and not as text. */
2
2
  export { Copyable, CopyButton, } from "./Copyable.js";
3
- export { DateValue, formatDateTime, formatRelative, } from "./Fecha.js";
3
+ export { DateValue, formatDateTime, formatRelative, } from "./DateValue.js";
4
4
  export { Meter, meterLevel, } from "./Meter.js";
5
5
  export { Metric, Metrics } from "./Metric.js";
6
6
  export { Avatar, UserChip, UserStack, } from "./Person.js";
package/dist/tokens.css CHANGED
@@ -630,6 +630,28 @@
630
630
  the corner of the eye. The bar uses it behind `motion-safe:`: with reduced
631
631
  motion it stands still. */
632
632
  --animate-progress-sweep: progress-sweep 1.6s var(--ease-in-out) infinite;
633
+
634
+ /* What appears only if waiting takes long enough to be seen: a skeleton. A read that
635
+ answers in 150ms showed a skeleton for one frame — a flash that says «something broke»
636
+ more than «loading». It waits 200ms invisible, then fades in. */
637
+ --animate-reveal-late: reveal var(--duration-quick) var(--ease-out) 200ms both;
638
+ }
639
+
640
+ @keyframes reveal {
641
+ from {
642
+ opacity: 0;
643
+ }
644
+ }
645
+
646
+ /* The focus ring ARRIVES: it closes in from a little farther out while it takes its colour,
647
+ in 160ms. A ring that pops is found; one that moves is found before the eye looks for it.
648
+ Only on the controls a person moves through — a dialog or a popover that takes the focus to
649
+ be announced keeps its own entrance — and only with motion allowed. */
650
+ @keyframes focus-in {
651
+ from {
652
+ outline-offset: 5px;
653
+ outline-color: transparent;
654
+ }
633
655
  }
634
656
 
635
657
  /* The segment is two fifths of its track: from just off the left edge (-100 % of
@@ -924,6 +946,26 @@
924
946
  font-variant-numeric: tabular-nums;
925
947
  }
926
948
 
949
+ @media (prefers-reduced-motion: no-preference) {
950
+ :where(
951
+ button,
952
+ a[href],
953
+ input,
954
+ textarea,
955
+ select,
956
+ summary,
957
+ [role="tab"],
958
+ [role="option"],
959
+ [role="gridcell"],
960
+ [role="row"],
961
+ [role="checkbox"],
962
+ [role="radio"],
963
+ [role="switch"]
964
+ ):focus-visible {
965
+ animation: focus-in var(--duration-quick) var(--ease-out);
966
+ }
967
+ }
968
+
927
969
  /* Thin and warm scrollbars. With six panels open, six grey system bars are
928
970
  six stripes that say nothing. */
929
971
  * {
@@ -1,11 +1,12 @@
1
1
  import type { BoardStages } from "../../components/listing/BoardColumns.js";
2
2
  import type { ListIdentity } from "../../components/listing/ListingBar.js";
3
3
  import type { SortField } from "../../components/listing/SortMenu.js";
4
+ import type { ViewIcon } from "../../components/listing/ViewSwitcher.js";
4
5
  import type { Surface } from "../../foundations/types.js";
5
6
  import type { DataColumn, DataTableCreate } from "../../primitives/table/DataTable.js";
6
7
  import type { RowAction } from "../../primitives/table/RowActions.js";
7
8
  import type { SortState } from "../../primitives/table/Table.js";
8
- import { type ListCalendar, type ListCrumb, type ListEmpty, type ListGantt, type ListGroup, type ListPivot, type ListPresent, type ListReport, type ListSheet, type ListTimeline, type RowOf } from "./types.js";
9
+ import { type ListCalendar, type ListCrumb, type ListEmpty, type ListGantt, type ListGroup, type ListPivot, type ListPresent, type ListReport, type ListSet, type ListSheet, type ListTimeline, type RowOf } from "./types.js";
9
10
  import type { ReactNode } from "react";
10
11
  type BarProps = {
11
12
  children?: ReactNode;
@@ -58,6 +59,17 @@ type BoardProps<T> = {
58
59
  * de datos CRUDOS sí las trae, porque ahí salen del archivo y el código no sabe
59
60
  * cuáles son. */
60
61
  type SheetProps<T> = Partial<ListSheet<T>>;
62
+ export type ListPresentProps<T> = {
63
+ /** Its own name — not one of the nine the package draws. What `present` asks for. */
64
+ id: string;
65
+ /** Its name in the switcher: the button's `title` and accessible name. */
66
+ label: string;
67
+ icon: ViewIcon;
68
+ /** It needs width — a grid of columns — so a narrow list (a phone, a panel) does not
69
+ * offer it. A map or a gallery reflows, and stays. */
70
+ wide?: boolean;
71
+ children: (set: ListSet<T>) => ReactNode;
72
+ };
61
73
  export type ListProps<T extends object> = {
62
74
  items: readonly T[];
63
75
  keyOf: (item: T) => string;
@@ -108,9 +120,16 @@ export type ListProps<T extends object> = {
108
120
  * actions — a list to read, like the lines of a record in view mode. One switch, so whoever
109
121
  * passes the handlers generically does not have to strip them one by one. */
110
122
  readOnly?: boolean;
111
- /** Los tres estados, en un solo lugar. */
123
+ /** Los estados del conjunto, en un solo lugar.
124
+ *
125
+ * `loading` es la PRIMERA lectura: no hay filas todavía, y se dibuja el esqueleto.
126
+ * `refreshing` es volver a preguntar con filas en la mano —se tipeó en la búsqueda, se
127
+ * cambió de página—: las filas se quedan y una barra discreta dice que se está
128
+ * preguntando. Cambiarlas por un esqueleto en cada pregunta hacía parpadear la tabla, y un
129
+ * clic podía caer sobre una fila que ya no estaba. */
112
130
  state?: {
113
131
  loading?: boolean;
132
+ refreshing?: boolean;
114
133
  error?: string | null;
115
134
  };
116
135
  /** Vacía POR UN FILTRO es otra situación: no falta contenido, sobra un
@@ -138,5 +157,6 @@ export declare namespace List {
138
157
  var Calendar: <T>(props: ListCalendar<T>) => ReactNode;
139
158
  var Gantt: <T>(props: ListGantt<T>) => ReactNode;
140
159
  var Timeline: <T>(props: ListTimeline<T>) => ReactNode;
160
+ var Present: <T>(props: ListPresentProps<T>) => ReactNode;
141
161
  }
142
162
  export {};
@@ -61,6 +61,23 @@ const TimelineSlot = slot("timeline", (_props) => null);
61
61
  const CalendarSlot = slot("calendar", (_props) => null);
62
62
  /** Each record as a span on a timeline, with its dependencies (`RecordGantt`). */
63
63
  const GanttSlot = slot("gantt", (_props) => null);
64
+ /* Through `unknown`, like `TableSlot`: the set both gives a `T` (`items`) and takes one
65
+ (`press`), so the props are invariant in `T`. */
66
+ const Present = slot("present", (_props) => null);
67
+ /** The presentations written with `List.Present`, checked once: an id that repeats or that
68
+ * shadows a built-in one would make the switcher offer two buttons for one thing. */
69
+ function facesOf(nodes) {
70
+ const faces = nodes
71
+ .filter((node) => slotOf(node) === "present")
72
+ .map((node) => propsOf(node))
73
+ .map(({ children, ...face }) => ({ ...face, render: children }));
74
+ for (const [at, face] of faces.entries()) {
75
+ if (face.id in PRESENT_OF || faces.findIndex((one) => one.id === face.id) !== at) {
76
+ throw new Error(`List.Present: «${face.id}» is already a presentation of this list`);
77
+ }
78
+ }
79
+ return faces;
80
+ }
64
81
  export function List({ items, keyOf, embedded, surface, group, selection: selectWith, sort, onSortChange, sortable, present, onPresentChange, onOpen: openWith, onPeek: peekWith, isPeeked, readOnly = false, rowClick, onChoose, state, filtered, empty, children, }) {
65
82
  const top = flatten(children);
66
83
  const onOpen = readOnly ? undefined : openWith;
@@ -86,13 +103,17 @@ export function List({ items, keyOf, embedded, surface, group, selection: select
86
103
  /* Las caras que hay son las que están escritas, EN ESE ORDEN. El conmutador
87
104
  no puede ofrecer una que no se pueda dibujar, porque la que se dibuja y la
88
105
  que se ofrece son el mismo nodo. */
89
- const presents = top
90
- .map((node) => FACES.find((face) => slotOf(node) === face))
91
- .filter((face) => face !== undefined)
92
- .map((face) => PRESENT_OF[face]);
106
+ const faces = facesOf(top);
107
+ const presents = top.flatMap((node) => {
108
+ const name = slotOf(node);
109
+ if (name === "present")
110
+ return [propsOf(node).id];
111
+ const builtIn = name === undefined ? undefined : PRESENT_OF[name];
112
+ return builtIn === undefined ? [] : [builtIn];
113
+ });
93
114
  /* La escotilla: lo que el arquetipo no previó se dibuja igual, debajo. Que
94
115
  aparezca algo acá es la señal de que al arquetipo le falta una pieza. */
95
- const rest = loose(top, ["bar", "metrics", ...FACES]);
116
+ const rest = loose(top, ["bar", "metrics", "present", ...FACES]);
96
117
  return (_jsx(ListView, { items: items, keyOf: keyOf, row: rows === undefined
97
118
  ? () => ({ name: "" })
98
119
  : readOnly
@@ -120,7 +141,7 @@ export function List({ items, keyOf, embedded, surface, group, selection: select
120
141
  : { onVisibleColumnIdsChange: table.onVisibleColumnIdsChange }),
121
142
  }), ...(board?.card === undefined ? {} : { card: board.card }), ...(board?.stages === undefined ? {} : { boardStages: board.stages }), ...(sheet?.columns === undefined || sheet.cell === undefined
122
143
  ? {}
123
- : { sheet: sheet }), ...(report === undefined ? {} : { report }), ...(pivot === undefined ? {} : { pivot }), ...(calendar === undefined ? {} : { calendar }), ...(gantt === undefined ? {} : { gantt }), ...(timeline === undefined ? {} : { timeline }), ...(presents.length === 0 ? {} : { presents }), ...(present === undefined ? {} : { present }), ...(onPresentChange === undefined ? {} : { onPresentChange }), ...(group === undefined ? {} : { group }), ...(selection === undefined ? {} : { selection }), ...(sort === undefined ? {} : { sort }), ...(onSortChange === undefined ? {} : { onSortChange }), ...(sortable === undefined ? {} : { sortable }), ...(onOpen === undefined ? {} : { onOpen }), ...(onPeek === undefined ? {} : { onPeek }), ...(isPeeked === undefined ? {} : { isPeeked }), ...(rowClick === undefined ? {} : { rowClick }), ...(onChoose === undefined || readOnly ? {} : { onChoose }), ...(state === undefined ? {} : { state }), ...(filtered === undefined ? {} : { filtered }), ...(empty === undefined ? {} : { empty }), children: rest.length === 0 ? undefined : rest }));
144
+ : { sheet: sheet }), ...(report === undefined ? {} : { report }), ...(pivot === undefined ? {} : { pivot }), ...(calendar === undefined ? {} : { calendar }), ...(gantt === undefined ? {} : { gantt }), ...(timeline === undefined ? {} : { timeline }), ...(faces.length === 0 ? {} : { faces }), ...(presents.length === 0 ? {} : { presents }), ...(present === undefined ? {} : { present }), ...(onPresentChange === undefined ? {} : { onPresentChange }), ...(group === undefined ? {} : { group }), ...(selection === undefined ? {} : { selection }), ...(sort === undefined ? {} : { sort }), ...(onSortChange === undefined ? {} : { onSortChange }), ...(sortable === undefined ? {} : { sortable }), ...(onOpen === undefined ? {} : { onOpen }), ...(onPeek === undefined ? {} : { onPeek }), ...(isPeeked === undefined ? {} : { isPeeked }), ...(rowClick === undefined ? {} : { rowClick }), ...(onChoose === undefined || readOnly ? {} : { onChoose }), ...(state === undefined ? {} : { state }), ...(filtered === undefined ? {} : { filtered }), ...(empty === undefined ? {} : { empty }), children: rest.length === 0 ? undefined : rest }));
124
145
  }
125
146
  List.Bar = Bar;
126
147
  List.Crumbs = Crumbs;
@@ -139,3 +160,4 @@ List.Pivot = Pivot;
139
160
  List.Calendar = CalendarSlot;
140
161
  List.Gantt = GanttSlot;
141
162
  List.Timeline = TimelineSlot;
163
+ List.Present = Present;
@@ -1,5 +1,5 @@
1
1
  import type { ListViewProps } from "./types.js";
2
- export declare function ListView<T extends object>({ items, keyOf, identity, crumbs, embedded, surface: requestedSurface, metrics, search, pagination, views, actions, row, rowHeader, columns, visibleColumnIds, onVisibleColumnIdsChange, card, boardStages, calendar, gantt, timeline, sheet, report, pivot, present: requested, presents, onPresentChange, sort, onSortChange, sortable, totals, rowActions, create, group, selection, onOpen, onPeek, rowClick, onChoose, state, filtered, empty, children, }: ListViewProps<T>): import("react").JSX.Element;
2
+ export declare function ListView<T extends object>({ items, keyOf, identity, crumbs, embedded, surface: requestedSurface, metrics, search, pagination, views, actions, row, rowHeader, columns, visibleColumnIds, onVisibleColumnIdsChange, card, boardStages, calendar, gantt, timeline, sheet, report, pivot, faces, present: requested, presents, onPresentChange, sort, onSortChange, sortable, totals, rowActions, create, group, selection, onOpen, onPeek, rowClick, onChoose, state, filtered, empty, children, }: ListViewProps<T>): import("react").JSX.Element;
3
3
  /** What a list offers when it fails and can be retried. */
4
4
  export declare function RetryAction({ onRetry }: {
5
5
  onRetry: () => void;