@sincpro/web-ui 1.2.0 → 1.3.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 (117) 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 +1 -1
  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/Row.d.ts +7 -0
  14. package/dist/components/listing/Row.js +7 -1
  15. package/dist/components/listing/ViewSwitcher.d.ts +7 -2
  16. package/dist/components/listing/index.d.ts +1 -1
  17. package/dist/components/listing/set.d.ts +4 -1
  18. package/dist/components/record/Breadcrumb.js +5 -1
  19. package/dist/components/search/CriteriaBuilder.d.ts +1 -1
  20. package/dist/components/search/CriteriaBuilder.js +3 -3
  21. package/dist/components/search/FilterBuilder.d.ts +1 -18
  22. package/dist/components/search/FilterBuilder.js +4 -4
  23. package/dist/components/search/JsonEditor.js +5 -1
  24. package/dist/components/search/SearchBar.d.ts +1 -2
  25. package/dist/components/search/criteria.d.ts +17 -1
  26. package/dist/components/search/index.d.ts +2 -1
  27. package/dist/components/search/index.js +1 -0
  28. package/dist/components/shell/Panes.d.ts +18 -0
  29. package/dist/components/shell/Panes.js +36 -0
  30. package/dist/components/shell/index.d.ts +1 -0
  31. package/dist/components/shell/index.js +2 -0
  32. package/dist/foundations/calendar.d.ts +3 -1
  33. package/dist/foundations/catalog.d.ts +27 -0
  34. package/dist/foundations/catalog.js +48 -0
  35. package/dist/foundations/copy.d.ts +29 -62
  36. package/dist/foundations/copy.es.js +4 -0
  37. package/dist/foundations/copy.js +19 -93
  38. package/dist/{primitives/field/context.d.ts → foundations/form.d.ts} +9 -0
  39. package/dist/{primitives/field/mode.js → foundations/form.js} +11 -0
  40. package/dist/foundations/grouping.d.ts +9 -3
  41. package/dist/foundations/index.d.ts +2 -0
  42. package/dist/foundations/index.js +5 -0
  43. package/dist/foundations/sticky.d.ts +4 -0
  44. package/dist/foundations/sticky.js +1 -0
  45. package/dist/primitives/choice/ComboBox.d.ts +1 -1
  46. package/dist/primitives/choice/ComboBox.js +4 -4
  47. package/dist/primitives/choice/RadioGroup.d.ts +1 -1
  48. package/dist/primitives/choice/RadioGroup.js +3 -3
  49. package/dist/primitives/choice/SearchField.js +1 -1
  50. package/dist/primitives/choice/SegmentedControl.d.ts +1 -1
  51. package/dist/primitives/choice/SegmentedControl.js +2 -2
  52. package/dist/primitives/choice/Slider.d.ts +1 -1
  53. package/dist/primitives/choice/Slider.js +2 -2
  54. package/dist/primitives/choice/Switch.d.ts +1 -1
  55. package/dist/primitives/choice/Switch.js +2 -2
  56. package/dist/primitives/date/Calendar.js +1 -1
  57. package/dist/primitives/date/DatePicker.d.ts +1 -1
  58. package/dist/primitives/date/DatePicker.js +2 -2
  59. package/dist/primitives/date/DateTime.d.ts +1 -1
  60. package/dist/primitives/date/DateTime.js +2 -2
  61. package/dist/primitives/field/Checks.d.ts +1 -1
  62. package/dist/primitives/field/Checks.js +4 -4
  63. package/dist/primitives/field/DropZone.d.ts +1 -1
  64. package/dist/primitives/field/DropZone.js +2 -2
  65. package/dist/primitives/field/Field.d.ts +2 -2
  66. package/dist/primitives/field/Field.js +3 -3
  67. package/dist/primitives/field/Suggest.d.ts +1 -1
  68. package/dist/primitives/field/Suggest.js +2 -2
  69. package/dist/primitives/field/index.d.ts +1 -3
  70. package/dist/primitives/field/index.js +4 -4
  71. package/dist/primitives/picker/Picker.d.ts +1 -1
  72. package/dist/primitives/picker/Picker.js +4 -4
  73. package/dist/primitives/{tag → picker}/TagsField.d.ts +2 -2
  74. package/dist/primitives/{tag → picker}/TagsField.js +5 -5
  75. package/dist/primitives/picker/index.d.ts +2 -0
  76. package/dist/primitives/picker/index.js +5 -1
  77. package/dist/primitives/table/CellCopy.js +1 -1
  78. package/dist/primitives/table/CellEditor.js +2 -4
  79. package/dist/primitives/table/DataTable.d.ts +11 -1
  80. package/dist/primitives/table/DataTable.js +111 -38
  81. package/dist/primitives/table/EditableTable.js +2 -2
  82. package/dist/primitives/table/PivotTable.js +0 -0
  83. package/dist/primitives/table/ReportTable.js +4 -3
  84. package/dist/primitives/table/SheetTable.js +8 -14
  85. package/dist/primitives/table/Table.js +2 -2
  86. package/dist/primitives/tabs/Tabs.d.ts +22 -4
  87. package/dist/primitives/tabs/Tabs.js +77 -23
  88. package/dist/primitives/tabs/index.d.ts +1 -1
  89. package/dist/primitives/tabs/index.js +1 -1
  90. package/dist/primitives/tag/Tags.js +4 -1
  91. package/dist/primitives/tag/index.d.ts +0 -2
  92. package/dist/primitives/tag/index.js +0 -3
  93. package/dist/primitives/value/Avatar.js +8 -1
  94. package/dist/primitives/value/{Fecha.d.ts → DateValue.d.ts} +5 -2
  95. package/dist/primitives/value/{Fecha.js → DateValue.js} +9 -8
  96. package/dist/primitives/value/Relation.d.ts +1 -1
  97. package/dist/primitives/value/Relation.js +2 -2
  98. package/dist/primitives/value/Value.js +4 -1
  99. package/dist/primitives/value/index.d.ts +1 -1
  100. package/dist/primitives/value/index.js +1 -1
  101. package/dist/views/list/List.d.ts +14 -1
  102. package/dist/views/list/List.js +28 -6
  103. package/dist/views/list/ListView.d.ts +1 -1
  104. package/dist/views/list/ListView.js +41 -14
  105. package/dist/views/list/index.d.ts +2 -2
  106. package/dist/views/list/presentations.js +3 -1
  107. package/dist/views/list/types.d.ts +49 -1
  108. package/dist/views/list/useNarrowList.d.ts +2 -2
  109. package/dist/views/list/useNarrowList.js +5 -5
  110. package/dist/views/record/Record.js +46 -42
  111. package/package.json +1 -1
  112. package/dist/primitives/field/context.js +0 -13
  113. package/dist/primitives/field/mode.d.ts +0 -9
  114. /package/dist/primitives/field/{copy.d.ts → clipboard.d.ts} +0 -0
  115. /package/dist/primitives/field/{copy.js → clipboard.js} +0 -0
  116. /package/dist/primitives/{tag/choices.d.ts → picker/tagChoices.d.ts} +0 -0
  117. /package/dist/primitives/{tag/choices.js → picker/tagChoices.js} +0 -0
@@ -9,7 +9,7 @@ import { SegmentedControl } from "../../primitives/choice/SegmentedControl.js";
9
9
  import { useNoteEntries } from "../../primitives/field/note.js";
10
10
  import { Callout } from "../../primitives/notice/Callout.js";
11
11
  import { Tag } from "../../primitives/tag/Tag.js";
12
- import { DateValue } from "../../primitives/value/Fecha.js";
12
+ import { DateValue } from "../../primitives/value/DateValue.js";
13
13
  import { Avatar } from "../../primitives/value/Person.js";
14
14
  import { Activity as ActivityIcon, ArrowRight, CheckCircle2, ChevronDown, ChevronRight, ChevronUp, CornerDownRight, History, Info, Lock, MessageSquare, OctagonAlert, PanelRightClose, PanelRightOpen, TriangleAlert, Zap, } from "lucide-react";
15
15
  import { Fragment, useId, useState } from "react";
@@ -1,3 +1,4 @@
1
+ import { type UiCopy } from "../../foundations/copy.js";
1
2
  import { type ReactNode } from "react";
2
3
  /**
3
4
  * The box to write in. What gets written is drawn by `Activity`, together with
@@ -51,9 +52,10 @@ export type MessageKind = {
51
52
  *
52
53
  * Es una función y no una constante porque sus palabras salen de la copia, y una
53
54
  * constante las congelaría con las que hubiera al cargar el módulo —o sea, las
54
- * de fábrica— por más que después se instale otra.
55
+ * de fábrica— por más que después se instale otra. Sin `copy`, las instaladas; un
56
+ * componente le pasa las de su rama.
55
57
  */
56
- export declare function writeANote(): MessageKind;
58
+ export declare function writeANote(copy?: UiCopy): MessageKind;
57
59
  export type MessageComposerProps = {
58
60
  /** The ways of writing this record takes. Its own list because it is the
59
61
  * application that knows whether there is WhatsApp, and to whose number. */
@@ -10,10 +10,10 @@ import { useState } from "react";
10
10
  *
11
11
  * Es una función y no una constante porque sus palabras salen de la copia, y una
12
12
  * constante las congelaría con las que hubiera al cargar el módulo —o sea, las
13
- * de fábrica— por más que después se instale otra.
13
+ * de fábrica— por más que después se instale otra. Sin `copy`, las instaladas; un
14
+ * componente le pasa las de su rama.
14
15
  */
15
- export function writeANote() {
16
- const copy = currentCopy();
16
+ export function writeANote(copy = currentCopy()) {
17
17
  return {
18
18
  id: "note",
19
19
  label: copy.activity.writeNote,
@@ -26,7 +26,7 @@ export function MessageComposer({ kinds, onSend, sending = false, footer, }) {
26
26
  /* La manera por default se arma acá adentro y no en la firma: un default de
27
27
  parámetro se evalúa en cada render, sí, pero escrito como constante del
28
28
  módulo congelaría las palabras de fábrica. */
29
- const ways = kinds ?? [writeANote()];
29
+ const ways = kinds ?? [writeANote(copy)];
30
30
  const [writing, setWriting] = useState(undefined);
31
31
  const [body, setBody] = useState("");
32
32
  const ready = body.trim() !== "" && !sending;
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { SurfaceProvider, useSurface } from "../../foundations/surface.js";
4
4
  import { Empty } from "../../primitives/notice/Notice.js";
5
- import { DateValue } from "../../primitives/value/Fecha.js";
5
+ import { DateValue } from "../../primitives/value/DateValue.js";
6
6
  import { Children } from "react";
7
7
  const DOT_TONE = {
8
8
  neutral: { canvas: "bg-muted", chrome: "bg-chrome-muted" },
@@ -5,7 +5,7 @@ import { currentCopy, fillCopy, useUiCopy } from "../../foundations/copy.js";
5
5
  import { cn, focusRing } from "../../foundations/variants.js";
6
6
  import { PopoverTrigger } from "../../primitives/overlay/PopoverTrigger.js";
7
7
  import { Tooltip, TooltipTrigger } from "../../primitives/overlay/Tooltip.js";
8
- import { DateValue } from "../../primitives/value/Fecha.js";
8
+ import { DateValue } from "../../primitives/value/DateValue.js";
9
9
  import { Link } from "react-aria-components";
10
10
  /* ==================================================================== *
11
11
  * THE ASSISTANT'S MARK — how a screen says «this came from the assistant».
@@ -3,8 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { acceptedMessage, applyWithUndo, } from "./assist.js";
4
4
  import { AssistLabel, AssistSourceLink } from "./AssistMark.js";
5
5
  import { useUiCopy } from "../../foundations/copy.js";
6
+ import { FieldIdsContext } from "../../foundations/form.js";
6
7
  import { Button, IconButton } from "../../primitives/button/Button.js";
7
- import { FieldIdsContext } from "../../primitives/field/context.js";
8
8
  import { useToastIfAny } from "../../primitives/notice/Toast.js";
9
9
  import { Check, X } from "lucide-react";
10
10
  import { useContext, useId } from "react";
@@ -79,7 +79,7 @@ function ReviewBody({ review }) {
79
79
  const copy = useUiCopy();
80
80
  const { groups, order, current, choose } = review;
81
81
  const state = chosenState(order, current);
82
- return (_jsxs("div", { className: "@container flex min-w-0 flex-col", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-center justify-between gap-x-3 gap-y-1 border-b border-line px-4 py-2", children: [_jsx(Checkbox, { checked: state === "all", mixed: state === "some", label: copy.assist.all, onChange: (on) => choose(on ? [...order] : []) }), _jsx("span", { className: "text-micro text-muted tabular-nums", children: fillCopy(copy.assist.chosen, { count: current.length, total: order.length }) })] }), groups.map((group, i) => (_jsxs("section", { className: "flex min-w-0 flex-col", children: [group.title === undefined ? null : (_jsx("h4", { className: "px-4 pt-3 pb-1 text-micro font-medium text-muted", children: group.title })), _jsx("ul", { className: "flex min-w-0 flex-col", children: group.changes.map((change) => (_jsx(ChangeRow, { change: change, on: current.includes(change.id), onToggle: (on) => choose(toggleChosen(order, current, change.id, on)) }, change.id))) })] }, group.title ?? i)))] }));
82
+ return (_jsxs("div", { className: "@container flex min-w-0 flex-col", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-center justify-between gap-x-3 gap-y-1 border-b border-line px-4 py-2", children: [_jsx(Checkbox, { checked: state === "all", mixed: state === "some", label: copy.assist.all, onChange: (on) => choose(on ? [...order] : []) }), _jsx("span", { className: "text-micro text-muted tabular-nums", children: fillCopy(copy.assist.chosen, { count: current.length, total: order.length }) })] }), groups.map((group, i) => (_jsxs("section", { className: "flex min-w-0 flex-col", children: [group.title === undefined ? null : (_jsx("h3", { className: "px-4 pt-3 pb-1 text-micro font-medium text-muted", children: group.title })), _jsx("ul", { className: "flex min-w-0 flex-col", children: group.changes.map((change) => (_jsx(ChangeRow, { change: change, on: current.includes(change.id), onToggle: (on) => choose(toggleChosen(order, current, change.id, on)) }, change.id))) })] }, group.title ?? i)))] }));
83
83
  }
84
84
  /** «Elegí al menos un cambio.», «Descartar» and «Aplicar N cambios». */
85
85
  function ReviewActions({ review, emphasis, onDiscard, after, }) {
@@ -102,7 +102,7 @@ export function ChangeReview(props) {
102
102
  const copy = useUiCopy();
103
103
  const review = useReview(props);
104
104
  const titleId = useId();
105
- return (_jsxs("section", { "aria-labelledby": titleId, "data-assist": "review", className: "flex min-w-0 flex-col overflow-clip rounded-box border border-dashed border-info/40 bg-surface", children: [_jsxs("header", { className: "flex min-w-0 flex-col gap-1 border-b border-line px-4 py-3", children: [_jsxs("span", { className: "flex min-w-0 flex-wrap items-baseline gap-x-3 gap-y-1", children: [_jsx(AssistLabel, {}), source === undefined ? null : _jsx(AssistSourceLink, { source: source })] }), _jsx("h3", { id: titleId, className: "text-lead font-semibold text-ink", children: title ?? copy.assist.review }), summary === undefined ? null : (_jsx("p", { className: "max-w-prose text-meta text-muted", children: summary }))] }), _jsx(ReviewBody, { review: review }), _jsx("footer", { className: "flex min-w-0 flex-wrap items-center justify-end gap-2 border-t border-line bg-ground px-4 py-2.5", children: _jsx(ReviewActions, { review: review, emphasis: emphasis, onDiscard: onDiscard }) })] }));
105
+ return (_jsxs("section", { "aria-labelledby": titleId, "data-assist": "review", className: "flex min-w-0 flex-col overflow-clip rounded-box border border-dashed border-info/40 bg-surface", children: [_jsxs("header", { className: "flex min-w-0 flex-col gap-1 border-b border-line px-4 py-3", children: [_jsxs("span", { className: "flex min-w-0 flex-wrap items-baseline gap-x-3 gap-y-1", children: [_jsx(AssistLabel, {}), source === undefined ? null : _jsx(AssistSourceLink, { source: source })] }), _jsx("h2", { id: titleId, className: "text-lead font-semibold text-ink", children: title ?? copy.assist.review }), summary === undefined ? null : (_jsx("p", { className: "max-w-prose text-meta text-muted", children: summary }))] }), _jsx(ReviewBody, { review: review }), _jsx("footer", { className: "flex min-w-0 flex-wrap items-center justify-end gap-2 border-t border-line bg-ground px-4 py-2.5", children: _jsx(ReviewActions, { review: review, emphasis: emphasis, onDiscard: onDiscard }) })] }));
106
106
  }
107
107
  ChangeReview.Group = Group;
108
108
  ChangeReview.Change = Change;
@@ -79,7 +79,10 @@ export function RecordCalendar({ items, keyOf, titleOf, startOf, endOf, allDayOf
79
79
  listPlugin,
80
80
  interactionPlugin,
81
81
  formaThemePlugin,
82
- ], locales: [esLocale], locale: copy.locale.toLowerCase().startsWith("es") ? "es" : "en", initialView: VIEW[view], ...(date === undefined ? {} : { initialDate: date }), headerToolbar: {
82
+ ], locales: [esLocale], locale: copy.locale.toLowerCase().startsWith("es") ? "es" : "en", initialView: VIEW[view], ...(date === undefined ? {} : { initialDate: date }),
83
+ /* «Hoy» is what the button SAYS, and so what it is called: the locale's hint named
84
+ it «Este mes» — a name that hides the visible word from voice control. */
85
+ todayText: copy.calendar.today, todayHint: () => copy.calendar.today, headerToolbar: {
83
86
  start: "prev,next today",
84
87
  center: "title",
85
88
  end: "dayGridMonth,timeGridWeek,timeGridDay,listWeek",
@@ -90,5 +90,5 @@ export function SheetGroup({ title, hint, icon: Icon, children, }) {
90
90
  /* A group title is ABOVE its labels, not one of them: at the labels' size and weight
91
91
  «Cliente» the group read like «Cliente» the field. One step up in size and weight,
92
92
  and a hairline that says where the group starts. */
93
- _jsxs("header", { className: "flex min-w-0 items-center gap-2 border-b border-line pb-1.5", children: [Icon === undefined ? null : (_jsx(Icon, { className: "size-(--size-icon-sm) shrink-0 text-muted", strokeWidth: 1.75, "aria-hidden": true })), _jsx("h3", { className: "min-w-0 truncate text-body font-semibold text-ink", children: title }), hint === undefined ? null : _jsx(Hint, { children: hint })] })) : hint === undefined ? null : (_jsx(Hint, { children: hint })), _jsx("div", { className: `flex min-w-0 flex-col ${byDensity(d, "gap-2.5", "gap-2")}`, children: children })] }));
93
+ _jsxs("header", { className: "flex min-w-0 items-center gap-2 border-b border-line pb-1.5", children: [Icon === undefined ? null : (_jsx(Icon, { className: "size-(--size-icon-sm) shrink-0 text-muted", strokeWidth: 1.75, "aria-hidden": true })), _jsx("h2", { className: "min-w-0 truncate text-body font-semibold text-ink", children: title }), hint === undefined ? null : _jsx(Hint, { children: hint })] })) : hint === undefined ? null : (_jsx(Hint, { children: hint })), _jsx("div", { className: `flex min-w-0 flex-col ${byDensity(d, "gap-2.5", "gap-2")}`, children: children })] }));
94
94
  }
@@ -1,5 +1,5 @@
1
+ import { type FormMode } from "../../foundations/form.js";
1
2
  import { type DialogContext } from "../../primitives/dialog/Dialog.js";
2
- import { type FormMode } from "../../primitives/field/mode.js";
3
3
  import type { FlagTone } from "../../primitives/notice/Flag.js";
4
4
  import type { DataColumn } from "../../primitives/table/DataTable.js";
5
5
  import { type EditColumn, type EditLayout } from "../../primitives/table/EditableTable.js";
@@ -4,12 +4,12 @@ import { commitDraft, filterLines, planRemoval, } from "./lines.js";
4
4
  import { useColumnControl, } from "../listing/columns.js";
5
5
  import { ColumnsMenu } from "../listing/ColumnsMenu.js";
6
6
  import { fillCopy, useUiCopy } from "../../foundations/copy.js";
7
+ import { FieldIdsContext } from "../../foundations/form.js";
8
+ import { useFormMode } from "../../foundations/form.js";
7
9
  import { Button, IconButton } from "../../primitives/button/Button.js";
8
10
  import { SearchField } from "../../primitives/choice/SearchField.js";
9
11
  import { Dialog } from "../../primitives/dialog/Dialog.js";
10
- import { FieldIdsContext } from "../../primitives/field/context.js";
11
12
  import { TextInput } from "../../primitives/field/Field.js";
12
- import { useFormMode } from "../../primitives/field/mode.js";
13
13
  import { Empty } from "../../primitives/notice/Notice.js";
14
14
  import { useToast } from "../../primitives/notice/Toast.js";
15
15
  import { EditableTable, } from "../../primitives/table/EditableTable.js";
@@ -101,7 +101,7 @@ export function ListingBar({ actions, title, description, counters, selection, s
101
101
  (count === undefined ? null : (_jsx("span", { role: "status", className: "shrink-0 text-meta text-muted tabular-nums", children: count === 1
102
102
  ? copy.list.countOne
103
103
  : fillCopy(copy.list.count, { count: count.toLocaleString(copy.locale) }) })));
104
- return (_jsx(SurfaceProvider, { value: "canvas", children: _jsxs("div", { className: `flex min-w-0 flex-wrap items-center gap-y-1.5 ${byDensity(d, "gap-x-3", "gap-x-2")}`, children: [title === undefined ? null : (_jsx("h3", { className: "min-w-0 shrink-0 truncate text-body font-semibold text-ink", children: title })), center === null ? null : (_jsx("div", { className: "flex min-w-0 flex-1 basis-(--container-name) items-center overflow-x-clip overflow-y-visible", children: center })), howMany, views === undefined ? null : (_jsx("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: views })), actions === undefined ? null : (_jsx("div", { className: `flex shrink-0 items-center gap-1.5 ${views === undefined ? "ml-auto" : ""}`, children: actions }))] }) }));
104
+ return (_jsx(SurfaceProvider, { value: "canvas", children: _jsxs("div", { className: `flex min-w-0 flex-wrap items-center gap-y-1.5 ${byDensity(d, "gap-x-3", "gap-x-2")}`, children: [title === undefined ? null : (_jsx("h2", { className: "min-w-0 shrink-0 truncate text-body font-semibold text-ink", children: title })), center === null ? null : (_jsx("div", { className: "flex min-w-0 flex-1 basis-(--container-name) items-center overflow-x-clip overflow-y-visible", children: center })), howMany, views === undefined ? null : (_jsx("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: views })), actions === undefined ? null : (_jsx("div", { className: `flex shrink-0 items-center gap-1.5 ${views === undefined ? "ml-auto" : ""}`, children: actions }))] }) }));
105
105
  }
106
106
  /* El campo de búsqueda va en el CENTRO de la línea, y centrado contra la
107
107
  línea: pide `--container-search` y los dos lados se reparten lo que sobra
@@ -127,3 +127,10 @@ export declare function GroupHeader({ field, label, count, detail, depth, open,
127
127
  open?: boolean;
128
128
  onToggle?: () => void;
129
129
  }): import("react").JSX.Element;
130
+ /** Under a pile that shows its first page: «Ver 38 más». Indented like the pile's rows. */
131
+ export declare function GroupMore({ left, depth, onMore, }: {
132
+ /** How many the pile counts that are not here yet. */
133
+ left: number;
134
+ depth?: number;
135
+ onMore: () => void;
136
+ }): import("react").JSX.Element;
@@ -127,7 +127,7 @@ export function RowHeader({ name, meta, tags, withSelection = false, withIcon =
127
127
  export function GroupHeader({ field, label, count, detail, depth = 0, open = true, onToggle, }) {
128
128
  const d = useDensity();
129
129
  const copy = useUiCopy();
130
- const content = (_jsxs(_Fragment, { children: [onToggle === undefined ? null : (_jsx(ChevronRight, { className: `size-(--size-icon-sm) shrink-0 text-muted transition-transform duration-(--duration-instant) ${open ? "rotate-90" : ""}`, strokeWidth: 2, "aria-hidden": true })), field === undefined ? null : (_jsx("span", { className: "shrink-0 text-micro text-muted/70", children: field })), _jsx("span", { className: "truncate text-micro font-medium text-ink", children: label }), count === undefined ? null : (_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: count.toLocaleString(copy.locale) })), detail === undefined ? null : (_jsx("span", { className: "shrink-0 text-micro tabular-nums text-muted", children: detail }))] }));
130
+ const content = (_jsxs(_Fragment, { children: [onToggle === undefined ? null : (_jsx(ChevronRight, { className: `size-(--size-icon-sm) shrink-0 text-muted transition-transform duration-(--duration-instant) ${open ? "rotate-90" : ""}`, strokeWidth: 2, "aria-hidden": true })), field === undefined ? null : (_jsx("span", { className: "shrink-0 text-micro text-muted", children: field })), _jsx("span", { className: "truncate text-micro font-medium text-ink", children: label }), count === undefined ? null : (_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: count.toLocaleString(copy.locale) })), detail === undefined ? null : (_jsx("span", { className: "shrink-0 text-micro tabular-nums text-muted", children: detail }))] }));
131
131
  /* La sangría va en el relleno de la izquierda y no en un margen: el
132
132
  encabezado tiene fondo y borde inferior, y un margen los cortaría antes del
133
133
  borde de la lista — la banda dejaría de leerse como una franja. El hover
@@ -137,3 +137,9 @@ export function GroupHeader({ field, label, count, detail, depth = 0, open = tru
137
137
  const mark = (_jsx("span", { "aria-hidden": true, className: "pointer-events-none absolute inset-x-1 inset-y-0.5 rounded-control group-hover/head:bg-ink/4" }));
138
138
  return onToggle === undefined ? (_jsx("div", { className: box, style: indent, children: content })) : (_jsxs("button", { type: "button", onClick: onToggle, "aria-expanded": open, style: indent, className: `group/head relative ${box} w-full text-left focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary`, children: [mark, content] }));
139
139
  }
140
+ /** Under a pile that shows its first page: «Ver 38 más». Indented like the pile's rows. */
141
+ export function GroupMore({ left, depth = 0, onMore, }) {
142
+ const d = useDensity();
143
+ const copy = useUiCopy();
144
+ return (_jsx("div", { className: `border-b border-line ${byDensity(d, "px-3 py-1", "px-2.5 py-0.5")}`, style: depth === 0 ? undefined : { paddingLeft: `calc(var(--spacing-indent) * ${depth})` }, children: _jsx("button", { type: "button", onClick: onMore, className: "rounded-control text-meta text-primary transition-colors duration-(--duration-instant) hover:underline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary", children: fillCopy(copy.table.pileMore, { count: left.toLocaleString(copy.locale) }) }) }));
145
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Surface } from "../../foundations/types.js";
2
- import type { LucideIcon } from "lucide-react";
2
+ import type { ComponentType } from "react";
3
3
  /** How the same list is shown: table, board, timeline.
4
4
  *
5
5
  * The icons go together and touching —a segmented group and not three loose
@@ -13,8 +13,13 @@ import type { LucideIcon } from "lucide-react";
13
13
  export interface ViewOption {
14
14
  id: string;
15
15
  label: string;
16
- icon: LucideIcon;
16
+ /** Any icon component — lucide's are what the package uses, not what it requires. */
17
+ icon: ViewIcon;
17
18
  }
19
+ export type ViewIcon = ComponentType<{
20
+ className?: string;
21
+ strokeWidth?: number;
22
+ }>;
18
23
  export declare function ViewSwitcher({ options, current, onSelect, surface, }: {
19
24
  options: readonly ViewOption[];
20
25
  current: string;
@@ -10,7 +10,7 @@ export { GroupHeader, Row } from "./Row.js";
10
10
  export { type Selection, useSelection } from "./selection.js";
11
11
  export { type SetControl, useSetControl } from "./set.js";
12
12
  export { toggleCollapsed, type TreeControl, useTreeControl, } from "./tree.js";
13
- export { type ViewOption, ViewSwitcher, } from "./ViewSwitcher.js";
13
+ export { type ViewIcon, type ViewOption, ViewSwitcher, } from "./ViewSwitcher.js";
14
14
  export { type BoardColumn, BoardColumns, type BoardStages, } from "./BoardColumns.js";
15
15
  export { type GroupingControl, useGroupingControl, } from "./grouping.js";
16
16
  export { GroupingLevels, type GroupingLevelsProps, } from "./GroupingLevels.js";
@@ -33,7 +33,10 @@ import { type Pages } from "./pages.js";
33
33
  * también; lo que se lee está en el cruce. Tampoco se
34
34
  * ordena: el orden de cada eje es el de sus claves.
35
35
  */
36
- export type ListPresent = "rows" | "table" | "board" | "sheet" | "report" | "pivot" | "calendar" | "gantt" | "timeline";
36
+ export type BuiltInPresent = "rows" | "table" | "board" | "sheet" | "report" | "pivot" | "calendar" | "gantt" | "timeline";
37
+ /** A presentation of a list: one the package draws, or the `id` of a `List.Present` the
38
+ * application wrote. `string & {}` keeps the built-in names in autocomplete. */
39
+ export type ListPresent = BuiltInPresent | (string & {});
37
40
  export interface SetControl<Row, Present extends string = ListPresent> extends Pages<Row> {
38
41
  present: Present;
39
42
  setPresent: (next: Present) => void;
@@ -69,7 +69,11 @@ export function Breadcrumb({ crumbs, surface: surfaceProp, folded = 3, }) {
69
69
  const shown = folds
70
70
  ? [crumbs[0], null, crumbs[crumbs.length - 1]]
71
71
  : crumbs;
72
- return (_jsx("nav", { "aria-label": copy.record.location, className: "flex min-w-0", children: _jsx(Breadcrumbs, { "aria-label": copy.record.location, className: "flex min-w-0 flex-wrap items-center gap-1", children: shown.map((crumb, i) => {
72
+ /* Named by WHERE it ends, not only by what it is: two records side by side (panes, a
73
+ panel next to the page) are two trails, and a screen reader lists landmarks by name. */
74
+ const last = crumbs[crumbs.length - 1];
75
+ const name = last === undefined ? copy.record.location : `${copy.record.location}: ${last.label}`;
76
+ return (_jsx("nav", { "aria-label": name, className: "flex min-w-0", children: _jsx(Breadcrumbs, { "aria-label": name, className: "flex min-w-0 flex-wrap items-center gap-1", children: shown.map((crumb, i) => {
73
77
  const key = crumb === null ? "folded" : `${i}-${crumb.label}`;
74
78
  const last = i === shown.length - 1;
75
79
  return (_jsxs(AriaBreadcrumb, { id: key, className: "flex min-w-0 items-center gap-1", children: [i === 0 ? null : (_jsx(ChevronRight, { className: `size-(--size-icon-sm) shrink-0 ${edge}`, strokeWidth: 2, "aria-hidden": true })), crumb === null ? (_jsx(Menu, { label: copy.record.location, title: copy.record.location, icon: MoreHorizontal, iconOnly: true, chevron: false, variant: "text", size: "sm", surface: surface === "chrome" ? "chrome" : "canvas", align: "start", groups: [
@@ -1,4 +1,4 @@
1
- import { type BuiltFilter } from "./FilterBuilder.js";
1
+ import type { BuiltFilter } from "./criteria.js";
2
2
  import { type ModelDefinition } from "./model.js";
3
3
  export interface BuiltSort {
4
4
  field: string;
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { blankGroup, ExpressionView, treeFromFilter, treeToFilter, WhereTree, } from "./FilterBuilder.js";
4
4
  import { JsonEditor } from "./JsonEditor.js";
5
5
  import { filterableNames, } from "./model.js";
6
- import { currentCopy, fillCopy, useUiCopy } from "../../foundations/copy.js";
6
+ import { fillCopy, useUiCopy } from "../../foundations/copy.js";
7
7
  import { GRAIN_ORDER } from "../../foundations/grouping.js";
8
8
  import { useSurface } from "../../foundations/surface.js";
9
9
  import { Rule } from "../../primitives/block/Block.js";
@@ -174,11 +174,11 @@ export function CriteriaBuilder({ model, initial, focus, onApply, onCancel, }) {
174
174
  parsed = JSON.parse(text);
175
175
  }
176
176
  catch {
177
- setRawError(currentCopy().builder.invalidJson);
177
+ setRawError(copy.builder.invalidJson);
178
178
  return;
179
179
  }
180
180
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
181
- setRawError(currentCopy().builder.notCriteria);
181
+ setRawError(copy.builder.notCriteria);
182
182
  return;
183
183
  }
184
184
  setRawError(null);
@@ -1,22 +1,5 @@
1
- import type { CriteriaJoin } from "./criteria.js";
1
+ import type { BuiltFilter, CriteriaJoin } from "./criteria.js";
2
2
  import { type ModelDefinition } from "./model.js";
3
- /** Una condición, tal como la declara el motor. `operator` sale del `ops` del campo, así que
4
- * lo que el constructor ofrece es exactamente lo que el modelo acepta. */
5
- export interface BuiltCondition {
6
- field: string;
7
- operator: string;
8
- value: unknown;
9
- }
10
- /** Lo que el filtro puede ser en su forma real: una condición, un grupo `all`/`any` de más
11
- * filtros —lo que el árbol arma, a cualquier profundidad—, o un `negate` que llegó de otro
12
- * lado y que el árbol no fabrica, pero que este componente sabe mostrar y editar como JSON. */
13
- export type BuiltFilter = BuiltCondition | {
14
- all: BuiltFilter[];
15
- } | {
16
- any: BuiltFilter[];
17
- } | {
18
- negate: BuiltFilter;
19
- };
20
3
  /** Si esto es una de las formas que el filtro reconoce — condición, o grupo `all`/`any`/
21
4
  * `negate`. Un JSON válido que no es ninguna de éstas —`{}`, `{"foo": 1}`, un array suelto—
22
5
  * no es un filtro que este componente sepa dibujar, y se trata como error de forma en vez de
@@ -355,13 +355,13 @@ export function WhereTree({ model, group, onChange, depth = 0, focus, }) {
355
355
  ? (node) => node?.scrollIntoView({ block: "nearest" })
356
356
  : undefined, className: `flex min-w-0 flex-wrap items-center gap-2 rounded-control ${focus !== undefined && child.field === focus
357
357
  ? "-m-1 p-1 ring-1 ring-primary/60"
358
- : ""}`, children: [_jsx("select", { value: child.field, onChange: (event) => setChild(index, {
358
+ : ""}`, children: [_jsx("select", { "aria-label": copy.builder.ruleField, value: child.field, onChange: (event) => setChild(index, {
359
359
  ...child,
360
360
  field: event.target.value,
361
361
  operator: model.fields[event.target.value]?.ops.includes(child.operator)
362
362
  ? child.operator
363
363
  : (model.fields[event.target.value]?.ops[0] ?? "="),
364
- }), className: `min-w-0 flex-1 basis-40 rounded-control border px-2 py-1 text-meta ${box}`, children: names.map((name) => (_jsx("option", { value: name, children: name }, name))) }), _jsx("select", { value: child.operator, onChange: (event) => setChild(index, { ...child, operator: event.target.value }), className: `min-w-0 flex-1 basis-36 rounded-control border px-2 py-1 text-meta ${box}`, children: (model.fields[child.field]?.ops ?? []).map((operator) => (_jsx("option", { value: operator, children: opLabel(operator) }, operator))) }), WITHOUT_VALUE.has(child.operator) ? (_jsx("span", { className: "flex-1 text-meta text-muted", children: copy.builder.noValue })) : WITH_RANGE.has(child.operator) ? (_jsxs("span", { className: "flex min-w-0 flex-1 basis-40 items-center gap-1", children: [_jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.value, onChange: (event) => setChild(index, { ...child, value: event.target.value }), placeholder: copy.builder.from, className: `min-w-0 flex-1 rounded-control border px-2 py-1 text-meta ${box}` }), _jsx("span", { className: "shrink-0 text-micro text-muted", children: copy.search.and }), _jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.upper ?? "", onChange: (event) => setChild(index, { ...child, upper: event.target.value }), placeholder: copy.builder.to, className: `min-w-0 flex-1 rounded-control border px-2 py-1 text-meta ${box}` })] })) : (_jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.value, onChange: (event) => setChild(index, { ...child, value: event.target.value }), placeholder: copy.builder.value, className: `min-w-0 flex-1 basis-40 rounded-control border px-2 py-1 text-meta ${box}` })), _jsxs("span", { className: "ml-auto flex shrink-0 items-center gap-2", children: [_jsx("button", { type: "button", "aria-label": copy.builder.addRule, onClick: () => addConditionAfter(index), className: "text-muted hover:text-ink", children: _jsx(Plus, { className: "size-4" }) }), _jsx("button", { type: "button", "aria-label": copy.builder.addNested, onClick: () => addGroupAfter(index), className: "text-muted hover:text-ink", children: _jsx(FolderTree, { className: "size-4" }) }), _jsx("button", { type: "button", "aria-label": copy.builder.dropRule, disabled: group.children.length === 1, onClick: () => removeAt(index), className: "text-muted disabled:opacity-30", children: _jsx(Trash2, { className: "size-4" }) })] })] }, index)))] }));
364
+ }), className: `min-w-0 flex-1 basis-40 rounded-control border px-2 py-1 text-meta ${box}`, children: names.map((name) => (_jsx("option", { value: name, children: name }, name))) }), _jsx("select", { "aria-label": copy.builder.ruleOperator, value: child.operator, onChange: (event) => setChild(index, { ...child, operator: event.target.value }), className: `min-w-0 flex-1 basis-36 rounded-control border px-2 py-1 text-meta ${box}`, children: (model.fields[child.field]?.ops ?? []).map((operator) => (_jsx("option", { value: operator, children: opLabel(operator) }, operator))) }), WITHOUT_VALUE.has(child.operator) ? (_jsx("span", { className: "flex-1 text-meta text-muted", children: copy.builder.noValue })) : WITH_RANGE.has(child.operator) ? (_jsxs("span", { className: "flex min-w-0 flex-1 basis-40 items-center gap-1", children: [_jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.value, onChange: (event) => setChild(index, { ...child, value: event.target.value }), placeholder: copy.builder.from, className: `min-w-0 flex-1 rounded-control border px-2 py-1 text-meta ${box}` }), _jsx("span", { className: "shrink-0 text-micro text-muted", children: copy.search.and }), _jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.upper ?? "", onChange: (event) => setChild(index, { ...child, upper: event.target.value }), placeholder: copy.builder.to, className: `min-w-0 flex-1 rounded-control border px-2 py-1 text-meta ${box}` })] })) : (_jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.value, onChange: (event) => setChild(index, { ...child, value: event.target.value }), placeholder: copy.builder.value, className: `min-w-0 flex-1 basis-40 rounded-control border px-2 py-1 text-meta ${box}` })), _jsxs("span", { className: "ml-auto flex shrink-0 items-center gap-2", children: [_jsx("button", { type: "button", "aria-label": copy.builder.addRule, onClick: () => addConditionAfter(index), className: "text-muted hover:text-ink", children: _jsx(Plus, { className: "size-4" }) }), _jsx("button", { type: "button", "aria-label": copy.builder.addNested, onClick: () => addGroupAfter(index), className: "text-muted hover:text-ink", children: _jsx(FolderTree, { className: "size-4" }) }), _jsx("button", { type: "button", "aria-label": copy.builder.dropRule, disabled: group.children.length === 1, onClick: () => removeAt(index), className: "text-muted disabled:opacity-30", children: _jsx(Trash2, { className: "size-4" }) })] })] }, index)))] }));
365
365
  }
366
366
  export function FilterBuilder({ model, initial, onApply, onCancel, }) {
367
367
  const names = filterableNames(model);
@@ -400,11 +400,11 @@ export function FilterBuilder({ model, initial, onApply, onCancel, }) {
400
400
  parsed = JSON.parse(text);
401
401
  }
402
402
  catch {
403
- setRawError(currentCopy().builder.invalidJson);
403
+ setRawError(copy.builder.invalidJson);
404
404
  return;
405
405
  }
406
406
  if (!isRecognizedFilter(parsed)) {
407
- setRawError(currentCopy().builder.notFilter);
407
+ setRawError(copy.builder.notFilter);
408
408
  return;
409
409
  }
410
410
  setRawError(null);
@@ -8,6 +8,7 @@ import { useTheme } from "../../foundations/theme.js";
8
8
  import { Button } from "../../primitives/button/index.js";
9
9
  import CodeMirror, { EditorView, Prec } from "@uiw/react-codemirror";
10
10
  import { Wand2 } from "lucide-react";
11
+ import { useId } from "react";
11
12
  /* ====================================================================== *
12
13
  * UN JSON QUE SE ESCRIBE A MANO, EN UN EDITOR DE VERDAD.
13
14
  *
@@ -56,11 +57,14 @@ const SURFACE_THEME = EditorView.theme({
56
57
  });
57
58
  export function JsonEditor({ value, onChange, error, height = "16rem", label, }) {
58
59
  const copy = useUiCopy();
60
+ const labelId = useId();
59
61
  const surface = useSurface();
60
62
  const { resolved } = useTheme();
61
63
  /* The chrome is dark in both themes; the canvas follows the theme. */
62
64
  const dark = surface === "chrome" || resolved === "dark";
63
- return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("p", { className: "text-micro text-muted", children: label ?? copy.builder.editor }), _jsxs(Button, { role: "ghost", size: "sm", disabled: value.trim() === "", onClick: () => onChange(formatJson(value)), children: [_jsx(Wand2, { className: "size-3" }), " ", copy.builder.format] })] }), _jsx("div", { className: `overflow-hidden rounded-control border ${error == null ? "border-ink/15" : "border-danger"}`, children: _jsx(CodeMirror, { value: value, onChange: onChange, height: height, theme: dark ? "dark" : "light", extensions: [
65
+ return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("p", { id: labelId, className: "text-micro text-muted", children: label ?? copy.builder.editor }), _jsxs(Button, { role: "ghost", size: "sm", disabled: value.trim() === "", onClick: () => onChange(formatJson(value)), children: [_jsx(Wand2, { className: "size-3" }), " ", copy.builder.format] })] }), _jsx("div", { className: `overflow-hidden rounded-control border ${error == null ? "border-ink/15" : "border-danger"}`, children: _jsx(CodeMirror, { value: value, onChange: onChange, height: height, theme: dark ? "dark" : "light", extensions: [
66
+ /* The editable area is CodeMirror's own element: named here or nowhere. */
67
+ EditorView.contentAttributes.of({ "aria-labelledby": labelId }),
64
68
  json(),
65
69
  linter(jsonParseLinter()),
66
70
  lintGutter(),
@@ -1,5 +1,4 @@
1
- import { type CriteriaJoin, type CriteriaPath, type SearchCriteria } from "./criteria.js";
2
- import type { BuiltCondition, BuiltFilter } from "./FilterBuilder.js";
1
+ import { type BuiltCondition, type BuiltFilter, type CriteriaJoin, type CriteriaPath, type SearchCriteria } from "./criteria.js";
3
2
  import type { ModelDefinition } from "./model.js";
4
3
  import type { Surface } from "../../foundations/types.js";
5
4
  import type { MenuGroup } from "../../primitives/menu/Menu.js";
@@ -1,5 +1,21 @@
1
- import type { BuiltCondition, BuiltFilter } from "./FilterBuilder.js";
2
1
  import type { GroupGrain, GroupLevel } from "../../foundations/grouping.js";
2
+ /** Una condición, tal como la declara el motor. `operator` sale del `ops` del campo, así que
3
+ * lo que el constructor ofrece es exactamente lo que el modelo acepta. */
4
+ export interface BuiltCondition {
5
+ field: string;
6
+ operator: string;
7
+ value: unknown;
8
+ }
9
+ /** Lo que el filtro puede ser en su forma real: una condición, un grupo `all`/`any` de más
10
+ * filtros —lo que el árbol arma, a cualquier profundidad—, o un `negate` que llegó de otro
11
+ * lado y que el árbol no fabrica, pero que este componente sabe mostrar y editar como JSON. */
12
+ export type BuiltFilter = BuiltCondition | {
13
+ all: BuiltFilter[];
14
+ } | {
15
+ any: BuiltFilter[];
16
+ } | {
17
+ negate: BuiltFilter;
18
+ };
3
19
  /** Cómo se juntan las instancias de un nodo. `not` niega lo que tiene adentro. */
4
20
  export type CriteriaJoin = "all" | "any" | "not";
5
21
  /**
@@ -1,5 +1,6 @@
1
+ export { type BuiltCondition, type BuiltFilter, } from "./criteria.js";
1
2
  export { type BuiltCriteria, type BuiltLevel, type BuiltSort, CriteriaBuilder, } from "./CriteriaBuilder.js";
2
- export { blankGroup, type BuiltCondition, type BuiltFilter, describeFilter, FilterBuilder, type TreeCondition, treeFromFilter, type TreeGroup, type TreeNode, treeToFilter, } from "./FilterBuilder.js";
3
+ export { blankGroup, describeFilter, FilterBuilder, type TreeCondition, treeFromFilter, type TreeGroup, type TreeNode, treeToFilter, } from "./FilterBuilder.js";
3
4
  export { formatJson, JsonEditor } from "./JsonEditor.js";
4
5
  export { SearchBar, type SearchBarProps, type SearchFacet, } from "./SearchBar.js";
5
6
  export { at, criteriaId, type CriteriaJoin, type CriteriaPath, filterCriteria, filtersOf, type GroupCriteria, groupCriteria, groupsOf, hasNamed, levelsOf, nextJoin, removeAt, replaceAt, type SearchCriteria, toggleNamed, whereOf, withLevels, } from "./criteria.js";
@@ -1,4 +1,5 @@
1
1
  /* Buscar con facetas, con los recortes metidos adentro del campo. */
2
+ export {} from "./criteria.js";
2
3
  export { CriteriaBuilder, } from "./CriteriaBuilder.js";
3
4
  export { blankGroup, describeFilter, FilterBuilder, treeFromFilter, treeToFilter, } from "./FilterBuilder.js";
4
5
  export { formatJson, JsonEditor } from "./JsonEditor.js";
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from "react";
2
+ declare const BASIS: {
3
+ readonly narrow: "@4xl:flex-2";
4
+ readonly wide: "@4xl:flex-3";
5
+ };
6
+ /** One column of the screen. `narrow` for the set you pick from, `wide` for the work. */
7
+ export declare function Pane({ children, size, label, }: {
8
+ children: ReactNode;
9
+ size?: keyof typeof BASIS;
10
+ /** What this pane is — «Extracto», «Por conciliar». Panes are regions a screen reader
11
+ * jumps between, so each one is named. */
12
+ label: string;
13
+ }): import("react").JSX.Element;
14
+ /** The panes, side by side on a desk and stacked when narrow. Fills the height it is given. */
15
+ export declare function Panes({ children }: {
16
+ children: ReactNode;
17
+ }): import("react").JSX.Element;
18
+ export {};
@@ -0,0 +1,36 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { HeaderSlotContext } from "./headerSlot.js";
4
+ import { stickyReset } from "../../foundations/sticky.js";
5
+ /* ==================================================================== *
6
+ * PANES — a screen of N sets side by side, each one with its own search.
7
+ *
8
+ * Context: Odoo's bank reconciliation is the case — the statement lines on one side, the
9
+ * record being matched and the journal items that could match it on the other, each with
10
+ * its own search, and the person going back and forth between them. A list that fills the
11
+ * page carries its bar up to the screen's header (`useHeaderSlot`), and there is ONE
12
+ * header: with two or three lists they fought over it, and each bar stuck to the page
13
+ * instead of to its own column.
14
+ *
15
+ * A pane is the answer: on a desk each pane SCROLLS ON ITS OWN — a long list on the left
16
+ * does not drag the record on the right out of sight — so what sticks inside it (a list's
17
+ * bar, a table's header) sticks to the pane's edge, and nothing inside goes up to the
18
+ * screen's header. Narrow, the panes stack and the page scrolls: a scroll inside a scroll
19
+ * on a phone is a list nobody can leave.
20
+ *
21
+ * It decides by ITS OWN width (it is the `@container`), not the window's: a workspace panel
22
+ * among four is narrow on a wide screen, and a view mounted without a container around it
23
+ * would never go side by side.
24
+ * ==================================================================== */
25
+ const BASIS = { narrow: "@4xl:flex-2", wide: "@4xl:flex-3" };
26
+ /** One column of the screen. `narrow` for the set you pick from, `wide` for the work. */
27
+ export function Pane({ children, size = "wide", label, }) {
28
+ return (_jsx(HeaderSlotContext.Provider, { value: null, children: _jsx("section", { "aria-label": label, style: stickyReset(),
29
+ /* Stacked it keeps its own height (the page scrolls); side by side it shares the
30
+ height and scrolls inside. */
31
+ className: `flex min-h-0 min-w-0 shrink-0 flex-col @4xl:shrink @4xl:basis-0 @4xl:overflow-y-auto ${BASIS[size]}`, children: children }) }));
32
+ }
33
+ /** The panes, side by side on a desk and stacked when narrow. Fills the height it is given. */
34
+ export function Panes({ children }) {
35
+ return (_jsx("div", { className: "@container flex h-full min-h-0 min-w-0 flex-1 flex-col", children: _jsx("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col divide-line @4xl:flex-row @4xl:divide-x max-@4xl:divide-y", children: children }) }));
36
+ }
@@ -1,2 +1,3 @@
1
1
  export { AppFrame } from "./AppFrame.js";
2
2
  export { type AppEntry, SystrayButton, TopBar, type TopMenu, } from "./TopBar.js";
3
+ export { Pane, Panes } from "./Panes.js";
@@ -1,3 +1,5 @@
1
1
  /* EL MARCO DE LA APLICACIÓN — lo que está arriba de toda vista. */
2
2
  export { AppFrame } from "./AppFrame.js";
3
3
  export { SystrayButton, TopBar, } from "./TopBar.js";
4
+ /* A screen of N sets side by side — the reconciliation: each pane scrolls on its own. */
5
+ export { Pane, Panes } from "./Panes.js";
@@ -1,4 +1,6 @@
1
- import type { GroupGrain } from "./grouping.js";
1
+ /** El grano de un nivel de fecha. Sin grano, un campo de fecha parte por
2
+ * instante y cada fila termina en su propio montón, que es no agrupar. */
3
+ export type GroupGrain = "day" | "week" | "month" | "quarter" | "year";
2
4
  /**
3
5
  * Una fecha recortada a la CLAVE de su grano.
4
6
  *
@@ -0,0 +1,27 @@
1
+ import { type ReactNode } from "react";
2
+ /** What changes, at any depth. A word is replaced; a section is merged. */
3
+ export type CopyOverride<C> = {
4
+ [K in keyof C]?: C[K] extends string ? string : C[K] extends readonly unknown[] ? C[K] : C[K] extends object ? CopyOverride<C[K]> : C[K];
5
+ };
6
+ /** `base` with `over` on top: sections merge, everything else is replaced. */
7
+ export declare function mergeCopyOver<C extends object>(base: C, over: CopyOverride<C> | undefined): C;
8
+ export type CopyCatalog<C extends object> = {
9
+ /** The factory words: what is there before anything is installed. */
10
+ english: C;
11
+ /** Sets the session's words — English with `over` on top — and returns them. */
12
+ install: (over?: CopyOverride<C>) => C;
13
+ /** The installed words: for what is not a component. */
14
+ current: () => C;
15
+ /** English with `over` on top, without installing it. */
16
+ merge: (over?: CopyOverride<C>) => C;
17
+ /** Changes the words for its branch, on top of what encloses it. */
18
+ Provider: (props: {
19
+ copy?: CopyOverride<C>;
20
+ children: ReactNode;
21
+ }) => ReactNode;
22
+ /** The words of this branch: the nearest `Provider`'s, or the installed ones. */
23
+ useCopy: () => C;
24
+ };
25
+ export declare function createCopy<C extends object>(english: C): CopyCatalog<C>;
26
+ /** `{name}` → value. A missing key stays visible in the template. */
27
+ export declare function fillCopy(template: string, params: Record<string, string | number>): string;
@@ -0,0 +1,48 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext, useMemo } from "react";
4
+ const isSection = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5
+ /** `base` with `over` on top: sections merge, everything else is replaced. */
6
+ export function mergeCopyOver(base, over) {
7
+ if (over === undefined)
8
+ return base;
9
+ const out = { ...base };
10
+ for (const [key, value] of Object.entries(over)) {
11
+ if (value === undefined)
12
+ continue;
13
+ const under = out[key];
14
+ out[key] = isSection(under) && isSection(value) ? mergeCopyOver(under, value) : value;
15
+ }
16
+ return out;
17
+ }
18
+ export function createCopy(english) {
19
+ let installed = english;
20
+ /* `null` and not English: a context keeps the value it was created with, so with the
21
+ factory words inside it a component without a provider would ignore `install`. */
22
+ const Context = createContext(null);
23
+ const current = () => installed;
24
+ const useCopy = () => useContext(Context) ?? installed;
25
+ function Provider({ copy, children }) {
26
+ const enclosing = useCopy();
27
+ const merged = useMemo(() => mergeCopyOver(enclosing, copy), [enclosing, copy]);
28
+ return _jsx(Context.Provider, { value: merged, children: children });
29
+ }
30
+ return {
31
+ english,
32
+ install: (over) => {
33
+ installed = mergeCopyOver(english, over);
34
+ return installed;
35
+ },
36
+ current,
37
+ merge: (over) => mergeCopyOver(english, over),
38
+ Provider,
39
+ useCopy,
40
+ };
41
+ }
42
+ /** `{name}` → value. A missing key stays visible in the template. */
43
+ export function fillCopy(template, params) {
44
+ return template.replace(/\{(\w+)\}/g, (whole, name) => {
45
+ const value = params[name];
46
+ return value === undefined ? whole : String(value);
47
+ });
48
+ }