@sincpro/web-ui 1.1.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 (121) 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/SearchBar.js +6 -1
  26. package/dist/components/search/criteria.d.ts +17 -1
  27. package/dist/components/search/index.d.ts +2 -1
  28. package/dist/components/search/index.js +1 -0
  29. package/dist/components/shell/Panes.d.ts +18 -0
  30. package/dist/components/shell/Panes.js +36 -0
  31. package/dist/components/shell/index.d.ts +1 -0
  32. package/dist/components/shell/index.js +2 -0
  33. package/dist/foundations/calendar.d.ts +3 -1
  34. package/dist/foundations/catalog.d.ts +27 -0
  35. package/dist/foundations/catalog.js +48 -0
  36. package/dist/foundations/copy.d.ts +33 -62
  37. package/dist/foundations/copy.es.js +6 -0
  38. package/dist/foundations/copy.js +21 -93
  39. package/dist/{primitives/field/context.d.ts → foundations/form.d.ts} +9 -0
  40. package/dist/{primitives/field/mode.js → foundations/form.js} +11 -0
  41. package/dist/foundations/grouping.d.ts +9 -3
  42. package/dist/foundations/index.d.ts +2 -0
  43. package/dist/foundations/index.js +5 -0
  44. package/dist/foundations/sticky.d.ts +4 -0
  45. package/dist/foundations/sticky.js +1 -0
  46. package/dist/primitives/choice/ComboBox.d.ts +1 -1
  47. package/dist/primitives/choice/ComboBox.js +4 -4
  48. package/dist/primitives/choice/RadioGroup.d.ts +1 -1
  49. package/dist/primitives/choice/RadioGroup.js +3 -3
  50. package/dist/primitives/choice/SearchField.js +1 -1
  51. package/dist/primitives/choice/SegmentedControl.d.ts +1 -1
  52. package/dist/primitives/choice/SegmentedControl.js +2 -2
  53. package/dist/primitives/choice/Slider.d.ts +1 -1
  54. package/dist/primitives/choice/Slider.js +2 -2
  55. package/dist/primitives/choice/Switch.d.ts +1 -1
  56. package/dist/primitives/choice/Switch.js +2 -2
  57. package/dist/primitives/date/Calendar.js +1 -1
  58. package/dist/primitives/date/DatePicker.d.ts +1 -1
  59. package/dist/primitives/date/DatePicker.js +2 -2
  60. package/dist/primitives/date/DateTime.d.ts +1 -1
  61. package/dist/primitives/date/DateTime.js +2 -2
  62. package/dist/primitives/field/Checks.d.ts +1 -1
  63. package/dist/primitives/field/Checks.js +4 -4
  64. package/dist/primitives/field/DropZone.d.ts +1 -1
  65. package/dist/primitives/field/DropZone.js +2 -2
  66. package/dist/primitives/field/Field.d.ts +2 -2
  67. package/dist/primitives/field/Field.js +3 -3
  68. package/dist/primitives/field/Suggest.d.ts +1 -1
  69. package/dist/primitives/field/Suggest.js +2 -2
  70. package/dist/primitives/field/index.d.ts +1 -3
  71. package/dist/primitives/field/index.js +4 -4
  72. package/dist/primitives/picker/Picker.d.ts +1 -1
  73. package/dist/primitives/picker/Picker.js +4 -4
  74. package/dist/primitives/{tag → picker}/TagsField.d.ts +2 -2
  75. package/dist/primitives/{tag → picker}/TagsField.js +5 -5
  76. package/dist/primitives/picker/index.d.ts +2 -0
  77. package/dist/primitives/picker/index.js +5 -1
  78. package/dist/primitives/table/CellCopy.js +1 -1
  79. package/dist/primitives/table/CellEditor.js +2 -4
  80. package/dist/primitives/table/DataTable.d.ts +11 -1
  81. package/dist/primitives/table/DataTable.js +137 -38
  82. package/dist/primitives/table/EditableTable.js +2 -2
  83. package/dist/primitives/table/PivotTable.js +0 -0
  84. package/dist/primitives/table/ReportTable.js +4 -3
  85. package/dist/primitives/table/SheetTable.js +8 -14
  86. package/dist/primitives/table/Table.d.ts +12 -3
  87. package/dist/primitives/table/Table.js +7 -7
  88. package/dist/primitives/table/rowWindow.d.ts +39 -0
  89. package/dist/primitives/table/rowWindow.js +177 -0
  90. package/dist/primitives/tabs/Tabs.d.ts +22 -4
  91. package/dist/primitives/tabs/Tabs.js +77 -23
  92. package/dist/primitives/tabs/index.d.ts +1 -1
  93. package/dist/primitives/tabs/index.js +1 -1
  94. package/dist/primitives/tag/Tags.js +4 -1
  95. package/dist/primitives/tag/index.d.ts +0 -2
  96. package/dist/primitives/tag/index.js +0 -3
  97. package/dist/primitives/value/Avatar.js +8 -1
  98. package/dist/primitives/value/{Fecha.d.ts → DateValue.d.ts} +5 -2
  99. package/dist/primitives/value/{Fecha.js → DateValue.js} +9 -8
  100. package/dist/primitives/value/Relation.d.ts +1 -1
  101. package/dist/primitives/value/Relation.js +2 -2
  102. package/dist/primitives/value/Value.js +4 -1
  103. package/dist/primitives/value/index.d.ts +1 -1
  104. package/dist/primitives/value/index.js +1 -1
  105. package/dist/views/list/List.d.ts +14 -1
  106. package/dist/views/list/List.js +28 -6
  107. package/dist/views/list/ListView.d.ts +1 -1
  108. package/dist/views/list/ListView.js +41 -14
  109. package/dist/views/list/index.d.ts +2 -2
  110. package/dist/views/list/presentations.js +3 -1
  111. package/dist/views/list/types.d.ts +49 -1
  112. package/dist/views/list/useNarrowList.d.ts +2 -2
  113. package/dist/views/list/useNarrowList.js +5 -5
  114. package/dist/views/record/Record.js +46 -42
  115. package/package.json +1 -1
  116. package/dist/primitives/field/context.js +0 -13
  117. package/dist/primitives/field/mode.d.ts +0 -9
  118. /package/dist/primitives/field/{copy.d.ts → clipboard.d.ts} +0 -0
  119. /package/dist/primitives/field/{copy.js → clipboard.js} +0 -0
  120. /package/dist/primitives/{tag/choices.d.ts → picker/tagChoices.d.ts} +0 -0
  121. /package/dist/primitives/{tag/choices.js → picker/tagChoices.js} +0 -0
@@ -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
+ }
@@ -1,18 +1,20 @@
1
- import { type ReactNode } from "react";
1
+ import { type CopyOverride, fillCopy } from "./catalog.js";
2
+ import type { ReactNode } from "react";
2
3
  /**
3
4
  * Chrome copy for the design system.
4
5
  *
5
6
  * The handbook rule: code is English; language is chosen at render. This
6
7
  * package must not bake product Spanish into buttons. English is the default
7
- * so a story still reads; the app overlays the session locale via
8
- * `UiCopyProvider`.
8
+ * so a story still reads; the app installs the session's language once
9
+ * (`installCopy(SPANISH_COPY)`), and `UiCopyProvider` changes a branch.
9
10
  *
10
11
  * Domain sentences (empty catalog, upload hint) stay out — those are the
11
12
  * product's catalogs, passed as props.
12
13
  *
13
- * `locale` is also a module store so `formatCount` — called from modules that
14
- * are not components — tracks the same BCP-47 the provider just set. A React
15
- * context cannot be read from `shared/format.ts`.
14
+ * The installed words are also what is not a component reads (`currentCopy`,
15
+ * `currentCopyLocale`): a sentence built in a function, a month named by the
16
+ * calendar. A provider does not change those — it is for a branch, not for the
17
+ * session (see `catalog.tsx`).
16
18
  */
17
19
  export type UiCopy = {
18
20
  /** BCP-47 for `toLocaleString` and `Intl`. */
@@ -125,6 +127,10 @@ export type UiCopy = {
125
127
  };
126
128
  search: {
127
129
  tools: string;
130
+ /** The name of the panel button once something is set: it shows the count, and what
131
+ * the button SHOWS has to be inside its name (WCAG 2.5.3) — `{label}` is `tools`. */
132
+ toolsSetOne: string;
133
+ toolsSetMany: string;
128
134
  editChip: string;
129
135
  editNamed: string;
130
136
  removeChip: string;
@@ -222,6 +228,8 @@ export type UiCopy = {
222
228
  min: string;
223
229
  max: string;
224
230
  distinct: string;
231
+ /** Under a pile that holds its first page and counts them all: ask for the rest. */
232
+ pileMore: string;
225
233
  };
226
234
  menu: {
227
235
  empty: string;
@@ -361,6 +369,8 @@ export type UiCopy = {
361
369
  weekOfYear: string;
362
370
  quarter: string;
363
371
  quarterOfYear: string;
372
+ /** The calendar's «go to today» button: its word and its name are the same. */
373
+ today: string;
364
374
  };
365
375
  /**
366
376
  * CÓMO SE LLAMA UN OPERADOR para quien lo lee.
@@ -499,6 +509,9 @@ export type UiCopy = {
499
509
  pilePageNote: string;
500
510
  limitPiles: string;
501
511
  /** El árbol de condiciones. */
512
+ /** The names of a rule's two choices — the field it asks about, and how it compares. */
513
+ ruleField: string;
514
+ ruleOperator: string;
502
515
  noValue: string;
503
516
  from: string;
504
517
  to: string;
@@ -737,63 +750,21 @@ export type UiCopy = {
737
750
  };
738
751
  };
739
752
  export declare const ENGLISH_COPY: UiCopy;
740
- /** The merged catalog the formatters read when they are not a component. */
741
- export declare function currentCopy(): UiCopy;
753
+ /** What changes of web-ui's words, at any depth: `{ search: { joins: { any: "o" } } }`. */
754
+ export type UiCopyOverride = CopyOverride<UiCopy>;
755
+ /** The installed words: for formatters and sentences that are not a component. */
756
+ export declare const currentCopy: () => UiCopy;
742
757
  /** BCP-47 the formatters read when they are not a component. */
743
758
  export declare function currentCopyLocale(): string;
744
- export type UiCopyOverride = {
745
- locale?: string;
746
- present?: Partial<UiCopy["present"]>;
747
- frame?: Partial<UiCopy["frame"]>;
748
- selection?: Partial<UiCopy["selection"]>;
749
- pager?: Partial<UiCopy["pager"]>;
750
- record?: Partial<UiCopy["record"]>;
751
- shell?: Partial<UiCopy["shell"]>;
752
- field?: Partial<UiCopy["field"]>;
753
- choice?: Partial<UiCopy["choice"]>;
754
- search?: Partial<UiCopy["search"]>;
755
- failed?: Partial<UiCopy["failed"]>;
756
- table?: Partial<UiCopy["table"]>;
757
- menu?: Partial<UiCopy["menu"]>;
758
- list?: Partial<UiCopy["list"]>;
759
- grouping?: Partial<UiCopy["grouping"]>;
760
- dates?: Partial<UiCopy["dates"]>;
761
- calendar?: Partial<UiCopy["calendar"]>;
762
- ops?: Record<string, string>;
763
- sorting?: Partial<UiCopy["sorting"]>;
764
- copyable?: Partial<UiCopy["copyable"]>;
765
- activity?: Partial<UiCopy["activity"]>;
766
- editable?: Partial<UiCopy["editable"]>;
767
- picker?: Partial<UiCopy["picker"]>;
768
- cell?: Partial<UiCopy["cell"]>;
769
- time?: Partial<UiCopy["time"]>;
770
- dialog?: Partial<UiCopy["dialog"]>;
771
- report?: Partial<UiCopy["report"]>;
772
- builder?: Partial<UiCopy["builder"]>;
773
- pivot?: Partial<UiCopy["pivot"]>;
774
- person?: Partial<UiCopy["person"]>;
775
- lines?: Partial<UiCopy["lines"]>;
776
- lineItems?: Partial<UiCopy["lineItems"]>;
777
- priority?: Partial<UiCopy["priority"]>;
778
- board?: Partial<UiCopy["board"]>;
779
- kanbanState?: Partial<UiCopy["kanbanState"]>;
780
- timeline?: Partial<UiCopy["timeline"]>;
781
- activityState?: Partial<UiCopy["activityState"]>;
782
- relation?: Partial<UiCopy["relation"]>;
783
- relationCard?: Partial<UiCopy["relationCard"]>;
784
- totals?: Partial<UiCopy["totals"]>;
785
- inlineEdit?: Partial<UiCopy["inlineEdit"]>;
786
- theme?: Partial<UiCopy["theme"]>;
787
- tree?: Partial<UiCopy["tree"]>;
788
- assist?: Partial<UiCopy["assist"]>;
789
- };
790
- export declare function mergeCopy(over?: UiCopyOverride): UiCopy;
791
- /** For tests and non-React callers that need a locale before a provider mounts. */
792
- export declare function installCopy(over?: UiCopyOverride): UiCopy;
793
- export declare function UiCopyProvider({ copy, children, }: {
759
+ /** English with `over` on top, without installing it. */
760
+ export declare const mergeCopy: (over?: UiCopyOverride) => UiCopy;
761
+ /** Sets the session's words: once, before the first draw — and the one global there is. */
762
+ export declare const installCopy: (over?: UiCopyOverride) => UiCopy;
763
+ /** Changes the words for its branch, on top of what encloses it. It never writes the
764
+ * global: a corner in another language does not change the rest of the screen. */
765
+ export declare const UiCopyProvider: (props: {
794
766
  copy?: UiCopyOverride;
795
767
  children: ReactNode;
796
- }): import("react").JSX.Element;
797
- export declare function useUiCopy(): UiCopy;
798
- /** `{name}` → value. A missing key stays visible in the template. */
799
- export declare function fillCopy(template: string, params: Record<string, string | number>): string;
768
+ }) => ReactNode;
769
+ export declare const useUiCopy: () => UiCopy;
770
+ export { fillCopy };
@@ -127,6 +127,8 @@ export const SPANISH_COPY = {
127
127
  },
128
128
  search: {
129
129
  tools: "Filtrar, agrupar y favoritos",
130
+ toolsSetOne: "{label}, {count} puesto",
131
+ toolsSetMany: "{label}, {count} puestos",
130
132
  editChip: "Clic para editar",
131
133
  editNamed: "Editar {label}",
132
134
  removeChip: "Quitar {label}: {value}",
@@ -197,6 +199,7 @@ export const SPANISH_COPY = {
197
199
  min: "mín {value}",
198
200
  max: "máx {value}",
199
201
  distinct: "{value} distintos",
202
+ pileMore: "Ver {count} más",
200
203
  },
201
204
  menu: { empty: "Nada coincide." },
202
205
  list: {
@@ -262,6 +265,7 @@ export const SPANISH_COPY = {
262
265
  weekOfYear: "Semana {n} de {year}",
263
266
  quarter: "T{n}",
264
267
  quarterOfYear: "T{n} de {year}",
268
+ today: "Hoy",
265
269
  },
266
270
  grouping: {
267
271
  label: "Agrupar por",
@@ -344,6 +348,8 @@ export const SPANISH_COPY = {
344
348
  pilesNote: "Sobre lo que plegaron —los totales de arriba, y «count»— no sobre las filas: eso es el filtro de más arriba.",
345
349
  pilePageNote: "Sólo el primer nivel: pedida una página, los niveles más profundos sólo contestan por los montones que sobrevivieron.",
346
350
  limitPiles: "Limitar cuántos montones vuelven",
351
+ ruleField: "Campo",
352
+ ruleOperator: "Comparación",
347
353
  noValue: "sin valor",
348
354
  from: "desde…",
349
355
  to: "hasta…",
@@ -1,6 +1,5 @@
1
1
  "use client";
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- import { createContext, useContext } from "react";
2
+ import { createCopy, fillCopy } from "./catalog.js";
4
3
  export const ENGLISH_COPY = {
5
4
  locale: "en-US",
6
5
  present: {
@@ -90,6 +89,8 @@ export const ENGLISH_COPY = {
90
89
  },
91
90
  search: {
92
91
  tools: "Filter, group and favorites",
92
+ toolsSetOne: "{label}, {count} applied",
93
+ toolsSetMany: "{label}, {count} applied",
93
94
  editChip: "Click to edit",
94
95
  editNamed: "Edit {label}",
95
96
  removeChip: "Remove {label}: {value}",
@@ -144,6 +145,7 @@ export const ENGLISH_COPY = {
144
145
  min: "min {value}",
145
146
  max: "max {value}",
146
147
  distinct: "{value} distinct",
148
+ pileMore: "Show {count} more",
147
149
  },
148
150
  menu: {
149
151
  empty: "Nothing matches.",
@@ -240,6 +242,7 @@ export const ENGLISH_COPY = {
240
242
  weekOfYear: "Week {n}, {year}",
241
243
  quarter: "Q{n}",
242
244
  quarterOfYear: "Q{n} {year}",
245
+ today: "Today",
243
246
  },
244
247
  grouping: {
245
248
  label: "Group by",
@@ -335,6 +338,8 @@ export const ENGLISH_COPY = {
335
338
  pilesNote: "Over what they folded — the totals above, and «count» — not over the rows: that is the filter above.",
336
339
  pilePageNote: "Only the first level: once a page is asked for, the deeper levels answer only for the piles that survived.",
337
340
  limitPiles: "Limit how many piles come back",
341
+ ruleField: "Field",
342
+ ruleOperator: "Comparison",
338
343
  noValue: "no value",
339
344
  from: "from…",
340
345
  to: "to…",
@@ -500,97 +505,20 @@ export const ENGLISH_COPY = {
500
505
  empty: "empty",
501
506
  },
502
507
  };
503
- let activeCopy = ENGLISH_COPY;
504
- /** The merged catalog the formatters read when they are not a component. */
505
- export function currentCopy() {
506
- return activeCopy;
507
- }
508
+ /* The mechanism is `catalog.tsx`, the same one an application uses for its own words. */
509
+ const ui = createCopy(ENGLISH_COPY);
510
+ /** The installed words: for formatters and sentences that are not a component. */
511
+ export const currentCopy = ui.current;
508
512
  /** BCP-47 the formatters read when they are not a component. */
509
513
  export function currentCopyLocale() {
510
- return activeCopy.locale;
511
- }
512
- function rememberCopy(copy) {
513
- activeCopy = copy;
514
- }
515
- /**
516
- * La copia del árbol, cuando alguien la puso.
517
- *
518
- * Arranca en `null` y NO en el inglés de fábrica: un contexto se queda con el
519
- * valor que tenía cuando se creó, así que con el inglés adentro un componente
520
- * sin provider ignoraba `installCopy` y seguía diciendo «grouped by» aunque la
521
- * aplicación entera estuviera en castellano. Dos verdades sobre la misma cosa.
522
- *
523
- * Con `null`, sin provider se lee la instalada, que es la única.
524
- */
525
- const CopyContext = createContext(null);
526
- export function mergeCopy(over) {
527
- if (over === undefined)
528
- return ENGLISH_COPY;
529
- return {
530
- locale: over.locale ?? ENGLISH_COPY.locale,
531
- present: { ...ENGLISH_COPY.present, ...over.present },
532
- frame: { ...ENGLISH_COPY.frame, ...over.frame },
533
- selection: { ...ENGLISH_COPY.selection, ...over.selection },
534
- pager: { ...ENGLISH_COPY.pager, ...over.pager },
535
- record: { ...ENGLISH_COPY.record, ...over.record },
536
- shell: { ...ENGLISH_COPY.shell, ...over.shell },
537
- field: { ...ENGLISH_COPY.field, ...over.field },
538
- choice: { ...ENGLISH_COPY.choice, ...over.choice },
539
- search: { ...ENGLISH_COPY.search, ...over.search },
540
- failed: { ...ENGLISH_COPY.failed, ...over.failed },
541
- table: { ...ENGLISH_COPY.table, ...over.table },
542
- menu: { ...ENGLISH_COPY.menu, ...over.menu },
543
- list: { ...ENGLISH_COPY.list, ...over.list },
544
- grouping: { ...ENGLISH_COPY.grouping, ...over.grouping },
545
- dates: { ...ENGLISH_COPY.dates, ...over.dates },
546
- calendar: { ...ENGLISH_COPY.calendar, ...over.calendar },
547
- ops: { ...ENGLISH_COPY.ops, ...over.ops },
548
- sorting: { ...ENGLISH_COPY.sorting, ...over.sorting },
549
- copyable: { ...ENGLISH_COPY.copyable, ...over.copyable },
550
- activity: { ...ENGLISH_COPY.activity, ...over.activity },
551
- editable: { ...ENGLISH_COPY.editable, ...over.editable },
552
- picker: { ...ENGLISH_COPY.picker, ...over.picker },
553
- cell: { ...ENGLISH_COPY.cell, ...over.cell },
554
- time: { ...ENGLISH_COPY.time, ...over.time },
555
- dialog: { ...ENGLISH_COPY.dialog, ...over.dialog },
556
- report: { ...ENGLISH_COPY.report, ...over.report },
557
- builder: { ...ENGLISH_COPY.builder, ...over.builder },
558
- pivot: { ...ENGLISH_COPY.pivot, ...over.pivot },
559
- person: { ...ENGLISH_COPY.person, ...over.person },
560
- lines: { ...ENGLISH_COPY.lines, ...over.lines },
561
- lineItems: { ...ENGLISH_COPY.lineItems, ...over.lineItems },
562
- priority: { ...ENGLISH_COPY.priority, ...over.priority },
563
- board: { ...ENGLISH_COPY.board, ...over.board },
564
- kanbanState: { ...ENGLISH_COPY.kanbanState, ...over.kanbanState },
565
- timeline: { ...ENGLISH_COPY.timeline, ...over.timeline },
566
- activityState: { ...ENGLISH_COPY.activityState, ...over.activityState },
567
- relation: { ...ENGLISH_COPY.relation, ...over.relation },
568
- relationCard: { ...ENGLISH_COPY.relationCard, ...over.relationCard },
569
- totals: { ...ENGLISH_COPY.totals, ...over.totals },
570
- inlineEdit: { ...ENGLISH_COPY.inlineEdit, ...over.inlineEdit },
571
- theme: { ...ENGLISH_COPY.theme, ...over.theme },
572
- tree: { ...ENGLISH_COPY.tree, ...over.tree },
573
- assist: { ...ENGLISH_COPY.assist, ...over.assist },
574
- };
575
- }
576
- /** For tests and non-React callers that need a locale before a provider mounts. */
577
- export function installCopy(over) {
578
- const merged = mergeCopy(over);
579
- rememberCopy(merged);
580
- return merged;
581
- }
582
- export function UiCopyProvider({ copy, children, }) {
583
- const merged = mergeCopy(copy);
584
- rememberCopy(merged);
585
- return _jsx(CopyContext.Provider, { value: merged, children: children });
586
- }
587
- export function useUiCopy() {
588
- return useContext(CopyContext) ?? currentCopy();
589
- }
590
- /** `{name}` → value. A missing key stays visible in the template. */
591
- export function fillCopy(template, params) {
592
- return template.replace(/\{(\w+)\}/g, (whole, name) => {
593
- const value = params[name];
594
- return value === undefined ? whole : String(value);
595
- });
514
+ return ui.current().locale;
596
515
  }
516
+ /** English with `over` on top, without installing it. */
517
+ export const mergeCopy = ui.merge;
518
+ /** Sets the session's words: once, before the first draw — and the one global there is. */
519
+ export const installCopy = ui.install;
520
+ /** Changes the words for its branch, on top of what encloses it. It never writes the
521
+ * global: a corner in another language does not change the rest of the screen. */
522
+ export const UiCopyProvider = ui.Provider;
523
+ export const useUiCopy = ui.useCopy;
524
+ export { fillCopy };
@@ -1,3 +1,4 @@
1
+ import { type ReactNode } from "react";
1
2
  /**
2
3
  * What a `Field` hands to the control inside it: the id its label points at, the ids that
3
4
  * describe it (hint, error), and whether it is in error.
@@ -28,3 +29,11 @@ export declare function useFieldIds(own: {
28
29
  describedBy: string | undefined;
29
30
  invalid: boolean;
30
31
  };
32
+ export type FormMode = "edit" | "read";
33
+ export declare function FormModeProvider({ value, children, }: {
34
+ value: FormMode;
35
+ children: ReactNode;
36
+ }): import("react").JSX.Element;
37
+ /** The mode of this field. The prop beats the context — a computed field stays
38
+ * in read mode inside a form that is being edited. */
39
+ export declare function useFormMode(explicit?: FormMode): FormMode;
@@ -1,6 +1,17 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { createContext, useContext } from "react";
4
+ export const FieldIdsContext = createContext(null);
5
+ /** The ids of the enclosing `Field`, resolved against the control's own props. */
6
+ export function useFieldIds(own) {
7
+ const field = useContext(FieldIdsContext);
8
+ return {
9
+ id: own.id ?? field?.id,
10
+ labelledBy: field?.labelId,
11
+ describedBy: own.describedBy ?? field?.describedBy,
12
+ invalid: own.invalid ?? field?.invalid ?? false,
13
+ };
14
+ }
4
15
  const Ctx = createContext("edit");
5
16
  export function FormModeProvider({ value, children, }) {
6
17
  return _jsx(Ctx.Provider, { value: value, children: children });
@@ -1,3 +1,4 @@
1
+ import { type GroupGrain } from "./calendar.js";
1
2
  /**
2
3
  * LA AGRUPACIÓN, en N niveles.
3
4
  *
@@ -43,9 +44,7 @@
43
44
  * identifica y se lee igual— y por eso el rótulo sólo se pone aparte donde hace
44
45
  * falta: en las fechas, que las nombra `system/calendar.ts`.
45
46
  */
46
- /** El grano de un nivel de fecha. Sin grano, un campo de fecha parte por
47
- * instante y cada fila termina en su propio montón, que es no agrupar. */
48
- export type GroupGrain = "day" | "week" | "month" | "quarter" | "year";
47
+ export type { GroupGrain } from "./calendar.js";
49
48
  /**
50
49
  * De GRUESO a fino. Es el orden en que se anidan los granos de un mismo campo, y
51
50
  * no es una preferencia: agrupar por día y después por mes daría montones de un
@@ -196,6 +195,13 @@ export type Grouping<T> = {
196
195
  /** Qué montones están plegados, por `pathKey(pile.path)`. */
197
196
  collapsed?: readonly string[];
198
197
  onToggle?: (key: string) => void;
198
+ /**
199
+ * Un montón contado afuera trae su PRIMERA página y su conteo entero: esto pide las
200
+ * siguientes. Con esto, cada presentación ofrece «Ver N más» debajo del montón que
201
+ * tiene menos filas de las que cuenta; quien guarda los datos agrega las que lleguen a
202
+ * `pile.items`.
203
+ */
204
+ onMore?: (pile: Pile<T>) => void;
199
205
  };
200
206
  /**
201
207
  * El árbol que hay que dibujar, o `null` si no se agrupa.
@@ -1,5 +1,7 @@
1
+ export { type CopyCatalog, type CopyOverride, createCopy, mergeCopyOver, } from "./catalog.js";
1
2
  export { currentCopy, currentCopyLocale, ENGLISH_COPY, fillCopy, installCopy, mergeCopy, type UiCopy, type UiCopyOverride, UiCopyProvider, useUiCopy, } from "./copy.js";
2
3
  export { byDensity, type Density, DensityProvider, useDensity, } from "./density.js";
4
+ export { type FieldIds, type FormMode, FormModeProvider, useFieldIds, useFormMode, } from "./form.js";
3
5
  export { bySurface, SurfaceProvider, useSurface } from "./surface.js";
4
6
  export { applyTheme, isTheme, type ResolvedTheme, resolveTheme, type Theme, THEME_ATTRIBUTE, ThemeProvider, type ThemeProviderProps, THEMES, useTheme, } from "./theme.js";
5
7
  export type { OpenTarget, Size, Surface, Tone } from "./types.js";
@@ -1,6 +1,11 @@
1
1
  /* What is shared by more than one family and belongs to none of them. */
2
+ /* THE MECHANISM of a copy catalog — web-ui's words are one; an application's own are another. */
3
+ export { createCopy, mergeCopyOver, } from "./catalog.js";
2
4
  export { currentCopy, currentCopyLocale, ENGLISH_COPY, fillCopy, installCopy, mergeCopy, UiCopyProvider, useUiCopy, } from "./copy.js";
3
5
  export { byDensity, DensityProvider, useDensity, } from "./density.js";
6
+ /* EL CONTRATO DE UN CONTROL — los ids que le da su `Field` y si se mira o se escribe. Lo
7
+ hablan los controles del paquete y los de la aplicación por igual. */
8
+ export { FormModeProvider, useFieldIds, useFormMode, } from "./form.js";
4
9
  /* EL TEMA — claro, oscuro o el del sistema operativo. Las paletas viven en `tokens.css`; esto
5
10
  sólo escribe `data-theme`. */
6
11
  export { bySurface, SurfaceProvider, useSurface } from "./surface.js";
@@ -35,6 +35,10 @@ export declare function useScrollBox<T extends HTMLElement>(options: {
35
35
  ref: RefObject<T | null>;
36
36
  className: string;
37
37
  style: CSSProperties;
38
+ /** `0` when the box scrolls: someone without a pointer has to be able to reach it to
39
+ * scroll it (WCAG 2.1.1) — a twelve-column report is unreadable past its third column
40
+ * otherwise. `undefined` when it grows with its content. */
41
+ tabIndex: 0 | undefined;
38
42
  };
39
43
  /**
40
44
  * Mide el alto de un elemento y devuelve el estilo que empuja la pila para sus
@@ -94,6 +94,7 @@ export function useScrollBox(options) {
94
94
  return {
95
95
  ref,
96
96
  className: suelta ? "overflow-clip" : "overflow-auto",
97
+ tabIndex: suelta ? undefined : 0,
97
98
  style: suelta
98
99
  ? {}
99
100
  : {
@@ -1,5 +1,5 @@
1
+ import { type FormMode } from "../../foundations/form.js";
1
2
  import { type ChoiceOption } from "./options.js";
2
- import { type FormMode } from "../field/mode.js";
3
3
  export type ComboBoxProps = {
4
4
  /** The chosen option's `value`, or `null` when nothing is chosen. */
5
5
  value: string | null;
@@ -1,14 +1,14 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { AriaLocale } from "../../foundations/aria.js";
4
+ import { useFieldIds } from "../../foundations/form.js";
5
+ import { useFormMode } from "../../foundations/form.js";
4
6
  import { labelOf } from "./options.js";
5
7
  import { FieldButton, FieldGroup, listOptionStyles, } from "../field/aria.js";
6
- import { useFieldIds } from "../field/context.js";
7
8
  import { ReadValue } from "../field/Field.js";
8
- import { useFormMode } from "../field/mode.js";
9
9
  import { Popover } from "../overlay/Popover.js";
10
10
  import { Check, ChevronDown } from "lucide-react";
11
- import { ComboBox as AriaComboBox, Input, ListBox, ListBoxItem, Text, useFilter, } from "react-aria-components";
11
+ import { ComboBox as AriaComboBox, Input, ListBox, ListBoxItem, useFilter, } from "react-aria-components";
12
12
  /**
13
13
  * One of a closed list, found by typing: a country, a currency, a tax regime, a unit.
14
14
  *
@@ -52,5 +52,5 @@ function EditableComboBox({ value, onChange, options, placeholder, disabled = fa
52
52
  /* `defaultItems` and not `items`: with `items` React Aria hands the filtering to the
53
53
  consumer. These are the same options every render — only the filtering is React
54
54
  Aria's. */
55
- defaultItems: options, value: value, onChange: (next) => onChange?.(next === null ? null : String(next)), defaultFilter: contains, isInvalid: invalid, isDisabled: disabled, className: "w-full", children: [_jsxs(FieldGroup, { children: [_jsx(Input, { placeholder: placeholder, className: "h-full min-w-0 flex-1 bg-transparent px-2.5 text-meta text-ink outline-0 placeholder:text-muted disabled:cursor-not-allowed" }), _jsx(FieldButton, { className: "mr-1", children: _jsx(ChevronDown, { "aria-hidden": true, className: "size-(--size-icon-md)", strokeWidth: 2 }) })] }), _jsx(Popover, { placement: "bottom start", className: "w-(--trigger-width)", children: _jsx(ListBox, { items: options, className: "max-h-[inherit] overflow-auto p-1 outline-0", children: (item) => (_jsx(ListBoxItem, { id: item.value, textValue: item.label, isDisabled: item.disabled, className: option, children: ({ isSelected }) => (_jsxs(_Fragment, { children: [_jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx(Text, { slot: "label", className: `truncate ${isSelected ? "font-medium" : ""}`, children: item.label }), item.description === undefined ? null : (_jsx(Text, { slot: "description", className: "truncate text-micro text-muted", children: item.description }))] }), _jsx("span", { className: "flex size-(--size-icon-md) shrink-0 items-center justify-center", children: isSelected ? (_jsx(Check, { "aria-hidden": true, className: "size-(--size-icon-md) text-primary forced-colors:text-[HighlightText]", strokeWidth: 2 })) : null })] })) })) }) })] }));
55
+ defaultItems: options, value: value, onChange: (next) => onChange?.(next === null ? null : String(next)), defaultFilter: contains, isInvalid: invalid, isDisabled: disabled, className: "w-full", children: [_jsxs(FieldGroup, { children: [_jsx(Input, { placeholder: placeholder, className: "h-full min-w-0 flex-1 bg-transparent px-2.5 text-meta text-ink outline-0 placeholder:text-muted disabled:cursor-not-allowed" }), _jsx(FieldButton, { className: "mr-1", children: _jsx(ChevronDown, { "aria-hidden": true, className: "size-(--size-icon-md)", strokeWidth: 2 }) })] }), _jsx(Popover, { placement: "bottom start", className: "w-(--trigger-width)", children: _jsx(ListBox, { items: options, className: "max-h-[inherit] overflow-auto p-1 outline-0", children: (item) => (_jsx(ListBoxItem, { id: item.value, textValue: item.label, isDisabled: item.disabled, className: option, children: ({ isSelected }) => (_jsxs(_Fragment, { children: [_jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: `truncate ${isSelected ? "font-medium" : ""}`, children: item.label }), item.description === undefined ? null : (_jsx("span", { className: "truncate text-micro text-muted", children: item.description }))] }), _jsx("span", { className: "flex size-(--size-icon-md) shrink-0 items-center justify-center", children: isSelected ? (_jsx(Check, { "aria-hidden": true, className: "size-(--size-icon-md) text-primary forced-colors:text-[HighlightText]", strokeWidth: 2 })) : null })] })) })) }) })] }));
56
56
  }
@@ -1,5 +1,5 @@
1
+ import { type FormMode } from "../../foundations/form.js";
1
2
  import { type ChoiceOption } from "./options.js";
2
- import { type FormMode } from "../field/mode.js";
3
3
  export type RadioGroupProps = {
4
4
  /** The chosen option's `value`, or `null` while nothing is chosen. */
5
5
  value: string | null;
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useFieldIds } from "../../foundations/form.js";
4
+ import { useFormMode } from "../../foundations/form.js";
3
5
  import { tv } from "../../foundations/variants.js";
4
6
  import { labelOf } from "./options.js";
5
- import { useFieldIds } from "../field/context.js";
6
7
  import { ReadValue } from "../field/Field.js";
7
- import { useFormMode } from "../field/mode.js";
8
8
  import { RadioButton, RadioField, RadioGroup as AriaRadioGroup, Text, } from "react-aria-components";
9
9
  /**
10
10
  * One of few, all in sight: the payment method of a sale, the kind of a contact.
@@ -46,5 +46,5 @@ export function RadioGroup({ value, onChange, options, orientation = "vertical",
46
46
  field does not look shorter than its neighbours in the same row. */
47
47
  className: orientation === "horizontal"
48
48
  ? "flex min-w-0 flex-wrap items-start gap-x-5 gap-y-2 py-2"
49
- : "flex min-w-0 flex-col gap-2.5 py-2", children: options.map((o) => (_jsxs(RadioField, { value: o.value, isDisabled: o.disabled, className: "flex min-w-0 flex-col data-disabled:opacity-45", children: [_jsx(RadioButton, { className: "group flex min-w-0 cursor-pointer items-start gap-2.5 text-meta text-ink disabled:cursor-not-allowed", children: (state) => (_jsxs(_Fragment, { children: [_jsx("span", { className: mark(state), children: _jsx("span", { className: `size-1.5 rounded-pill bg-surface transition-transform duration-(--duration-instant) ${state.isSelected ? "scale-100" : "scale-0"}` }) }), _jsx("span", { className: "min-w-0", children: o.label })] })) }), o.description === undefined ? null : (_jsx(Text, { slot: "description", className: "ml-6.5 text-micro text-muted", children: o.description }))] }, o.value))) }));
49
+ : "flex min-w-0 flex-col gap-2.5 py-2", children: options.map((o) => (_jsxs(RadioField, { value: o.value, isDisabled: o.disabled, className: "flex min-w-0 flex-col", children: [_jsx(RadioButton, { className: "group flex min-w-0 cursor-pointer items-start gap-2.5 text-meta text-ink disabled:cursor-not-allowed data-disabled:opacity-45", children: (state) => (_jsxs(_Fragment, { children: [_jsx("span", { className: mark(state), children: _jsx("span", { className: `size-1.5 rounded-pill bg-surface transition-transform duration-(--duration-instant) ${state.isSelected ? "scale-100" : "scale-0"}` }) }), _jsx("span", { className: "min-w-0", children: o.label })] })) }), o.description === undefined ? null : (_jsx(Text, { slot: "description", className: "ml-6.5 text-micro text-muted", children: o.description }))] }, o.value))) }));
50
50
  }
@@ -2,8 +2,8 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { AriaLocale } from "../../foundations/aria.js";
4
4
  import { useUiCopy } from "../../foundations/copy.js";
5
+ import { useFieldIds } from "../../foundations/form.js";
5
6
  import { FieldButton, FieldGroup } from "../field/aria.js";
6
- import { useFieldIds } from "../field/context.js";
7
7
  import { Search, X } from "lucide-react";
8
8
  import { Input, SearchField as AriaSearchField } from "react-aria-components";
9
9
  /** The trailing «…» of a placeholder is a visual cue that typing is expected, not part of
@@ -1,5 +1,5 @@
1
+ import { type FormMode } from "../../foundations/form.js";
1
2
  import { type SegmentOption } from "./options.js";
2
- import { type FormMode } from "../field/mode.js";
3
3
  export type SegmentedControlProps = {
4
4
  value: string;
5
5
  onChange?: (next: string) => void;
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useFieldIds } from "../../foundations/form.js";
4
+ import { useFormMode } from "../../foundations/form.js";
3
5
  import { tv } from "../../foundations/variants.js";
4
6
  import { labelOf } from "./options.js";
5
- import { useFieldIds } from "../field/context.js";
6
7
  import { ReadValue } from "../field/Field.js";
7
- import { useFormMode } from "../field/mode.js";
8
8
  import { ToggleButton, ToggleButtonGroup } from "react-aria-components";
9
9
  /**
10
10
  * Switching what is looked at: the period of a report ("Día · Semana · Mes"), the grouping of
@@ -1,4 +1,4 @@
1
- import { type FormMode } from "../field/mode.js";
1
+ import { type FormMode } from "../../foundations/form.js";
2
2
  export type SliderProps = {
3
3
  value: number;
4
4
  onChange?: (next: number) => void;
@@ -2,10 +2,10 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { AriaLocale } from "../../foundations/aria.js";
4
4
  import { useUiCopy } from "../../foundations/copy.js";
5
+ import { useFieldIds } from "../../foundations/form.js";
6
+ import { useFormMode } from "../../foundations/form.js";
5
7
  import { tv } from "../../foundations/variants.js";
6
- import { useFieldIds } from "../field/context.js";
7
8
  import { ReadValue } from "../field/Field.js";
8
- import { useFormMode } from "../field/mode.js";
9
9
  import { Slider as AriaSlider, SliderFill, SliderOutput, SliderThumb, SliderTrack, } from "react-aria-components";
10
10
  /**
11
11
  * A number in a range, where the position says as much as the figure: a discount, a
@@ -1,4 +1,4 @@
1
- import { type FormMode } from "../field/mode.js";
1
+ import { type FormMode } from "../../foundations/form.js";
2
2
  import { type ReactNode } from "react";
3
3
  export type SwitchProps = {
4
4
  checked: boolean;
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useFieldIds } from "../../foundations/form.js";
4
+ import { useFormMode } from "../../foundations/form.js";
3
5
  import { tv } from "../../foundations/variants.js";
4
- import { useFieldIds } from "../field/context.js";
5
- import { useFormMode } from "../field/mode.js";
6
6
  import { Check, Minus } from "lucide-react";
7
7
  import { useId } from "react";
8
8
  import { Switch as AriaSwitch } from "react-aria-components";