@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
@@ -44,7 +44,7 @@ const YEARS_LISTED = 100;
44
44
  function Header({ jump }) {
45
45
  const { direction } = useLocale();
46
46
  const [Prev, Next] = direction === "rtl" ? [ChevronRight, ChevronLeft] : [ChevronLeft, ChevronRight];
47
- return (_jsxs("header", { className: "mb-2 flex items-center gap-1", children: [_jsx(AriaButton, { slot: "previous", className: navButton, children: _jsx(Prev, { "aria-hidden": true, className: "size-(--size-icon-md)", strokeWidth: 2 }) }), jump ? (_jsxs("div", { className: "flex flex-1 items-center justify-center", children: [_jsx(CalendarMonthPicker, { format: "long", children: (aria) => _jsx(Jump, { ...aria }) }), _jsx(CalendarYearPicker, { visibleYears: YEARS_LISTED, children: (aria) => _jsx(Jump, { ...aria }) })] })) : (_jsx(CalendarHeading, { className: "flex-1 text-center text-body font-semibold text-ink first-letter:uppercase" })), _jsx(AriaButton, { slot: "next", className: navButton, children: _jsx(Next, { "aria-hidden": true, className: "size-(--size-icon-md)", strokeWidth: 2 }) })] }));
47
+ return (_jsxs("div", { className: "mb-2 flex items-center gap-1", children: [_jsx(AriaButton, { slot: "previous", className: navButton, children: _jsx(Prev, { "aria-hidden": true, className: "size-(--size-icon-md)", strokeWidth: 2 }) }), jump ? (_jsxs("div", { className: "flex flex-1 items-center justify-center", children: [_jsx(CalendarMonthPicker, { format: "long", children: (aria) => _jsx(Jump, { ...aria }) }), _jsx(CalendarYearPicker, { visibleYears: YEARS_LISTED, children: (aria) => _jsx(Jump, { ...aria }) })] })) : (_jsx(CalendarHeading, { className: "flex-1 text-center text-body font-semibold text-ink first-letter:uppercase" })), _jsx(AriaButton, { slot: "next", className: navButton, children: _jsx(Next, { "aria-hidden": true, className: "size-(--size-icon-md)", strokeWidth: 2 }) })] }));
48
48
  }
49
49
  function WeekDays() {
50
50
  return (_jsx(CalendarGridHeader, { children: (day) => (_jsx(CalendarHeaderCell, { className: "pb-1 text-micro font-medium text-muted", children: day })) }));
@@ -1,4 +1,4 @@
1
- import { type FormMode } from "../field/mode.js";
1
+ import { type FormMode } from "../../foundations/form.js";
2
2
  /** The typed segments inside the field box — shared with `TimeField` and `DateTimePicker`
3
3
  * (internal: not in the family barrel). */
4
4
  export declare function Segments({ slot }: {
@@ -3,12 +3,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { DateFormatter, parseDate } from "@internationalized/date";
4
4
  import { AriaLocale } from "../../foundations/aria.js";
5
5
  import { useUiCopy } from "../../foundations/copy.js";
6
+ import { useFieldIds } from "../../foundations/form.js";
7
+ import { useFormMode } from "../../foundations/form.js";
6
8
  import { tv } from "../../foundations/variants.js";
7
9
  import { Calendar, RangeCalendar } from "./Calendar.js";
8
10
  import { FieldButton, FieldGroup } from "../field/aria.js";
9
- import { useFieldIds } from "../field/context.js";
10
11
  import { ReadValue } from "../field/Field.js";
11
- import { useFormMode } from "../field/mode.js";
12
12
  import { Popover } from "../overlay/Popover.js";
13
13
  import { CalendarDays } from "lucide-react";
14
14
  import { DateField as AriaDateField, DateInput, DatePicker as AriaDatePicker, DateRangePicker as AriaDateRangePicker, DateSegment, Dialog, } from "react-aria-components";
@@ -1,4 +1,4 @@
1
- import { type FormMode } from "../field/mode.js";
1
+ import { type FormMode } from "../../foundations/form.js";
2
2
  type HourCycle = 12 | 24;
3
3
  type Shared = {
4
4
  id?: string;
@@ -3,12 +3,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { DateFormatter, parseDateTime, parseTime, toCalendarDate, } from "@internationalized/date";
4
4
  import { AriaLocale } from "../../foundations/aria.js";
5
5
  import { useUiCopy } from "../../foundations/copy.js";
6
+ import { useFieldIds } from "../../foundations/form.js";
7
+ import { useFormMode } from "../../foundations/form.js";
6
8
  import { Calendar } from "./Calendar.js";
7
9
  import { Segments } from "./DatePicker.js";
8
10
  import { FieldButton, FieldGroup } from "../field/aria.js";
9
- import { useFieldIds } from "../field/context.js";
10
11
  import { ReadValue } from "../field/Field.js";
11
- import { useFormMode } from "../field/mode.js";
12
12
  import { Popover } from "../overlay/Popover.js";
13
13
  import { CalendarDays } from "lucide-react";
14
14
  import { DatePicker as AriaDatePicker, Dialog, TimeField as AriaTimeField, } from "react-aria-components";
@@ -1,4 +1,4 @@
1
- import { type FormMode } from "./mode.js";
1
+ import { type FormMode } from "../../foundations/form.js";
2
2
  export interface CheckItem {
3
3
  value: string;
4
4
  label: string;
@@ -1,11 +1,11 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useUiCopy } from "../../foundations/copy.js";
4
- import { useFieldIds } from "./context.js";
5
- import { useFormMode } from "./mode.js";
4
+ import { useFieldIds } from "../../foundations/form.js";
5
+ import { useFormMode } from "../../foundations/form.js";
6
6
  import { Check, Minus } from "lucide-react";
7
7
  import { useId } from "react";
8
- import { Checkbox as AriaCheckbox, ListBox, ListBoxItem, Text, } from "react-aria-components";
8
+ import { Checkbox as AriaCheckbox, ListBox, ListBoxItem, } from "react-aria-components";
9
9
  function Mark({ on, mixed, focus = false, invalid = false, }) {
10
10
  return (_jsx("span", { "aria-hidden": true, className: `inline-flex size-4 shrink-0 items-center justify-center rounded-mark border transition-colors duration-(--duration-instant) forced-color-adjust-none ${on
11
11
  ? "border-primary bg-primary text-surface forced-colors:border-[Highlight] forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]"
@@ -68,5 +68,5 @@ export function CheckList({ label, items, value, onChange, mono = false, emptyLa
68
68
  }
69
69
  return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1.5", children: [_jsx("p", { id: labelId, className: "text-micro text-muted", children: label }), _jsx(ListBox, { "aria-labelledby": labelId, "aria-describedby": describedBy, items: items, selectionMode: "multiple", selectedKeys: value, onSelectionChange: (keys) => onChange?.(valuesOf(keys, items)), disabledKeys: items.filter((item) => item.disabled).map((item) => item.value), shouldFocusWrap: true, className: "flex flex-col overflow-hidden rounded-box border border-line bg-surface outline-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary", children: (item) => (_jsx(ListBoxItem, { id: item.value, textValue: item.label, className: ({ isSelected, isFocusVisible, isDisabled }) => `flex min-w-0 cursor-pointer items-center gap-2.5 border-b border-line px-3 py-2 text-left outline-0 last:border-b-0 ${isFocusVisible
70
70
  ? "outline-2 -outline-offset-2 outline-primary forced-colors:outline-[Highlight]"
71
- : ""} ${isDisabled ? "cursor-not-allowed opacity-45" : ""} ${isSelected ? "bg-primary/8" : "hover:bg-ink/4"}`, children: ({ isSelected }) => (_jsxs(_Fragment, { children: [_jsx(Mark, { on: isSelected, mixed: false }), _jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsx(Text, { slot: "label", className: `truncate text-meta text-ink ${mono ? "font-mono" : ""}`, children: item.label }), item.detail === undefined ? null : (_jsx(Text, { slot: "description", className: "truncate text-micro text-muted", children: item.detail }))] })] })) })) })] }));
71
+ : ""} ${isDisabled ? "cursor-not-allowed opacity-45" : ""} ${isSelected ? "bg-primary/8" : "hover:bg-ink/4"}`, children: ({ isSelected }) => (_jsxs(_Fragment, { children: [_jsx(Mark, { on: isSelected, mixed: false }), _jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: `truncate text-meta text-ink ${mono ? "font-mono" : ""}`, children: item.label }), item.detail === undefined ? null : (_jsx("span", { className: "truncate text-micro text-muted", children: item.detail }))] })] })) })) })] }));
72
72
  }
@@ -1,4 +1,4 @@
1
- import { type FormMode } from "./mode.js";
1
+ import { type FormMode } from "../../foundations/form.js";
2
2
  /**
3
3
  * Drop a file, or pick one. Progress is a real bar; failure is a sentence.
4
4
  *
@@ -1,11 +1,11 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useUiCopy } from "../../foundations/copy.js";
4
+ import { useFieldIds } from "../../foundations/form.js";
5
+ import { useFormMode } from "../../foundations/form.js";
4
6
  import { cn } from "../../foundations/variants.js";
5
7
  import { Button, buttonClasses } from "../button/Button.js";
6
- import { useFieldIds } from "./context.js";
7
8
  import { ReadValue } from "./Field.js";
8
- import { useFormMode } from "./mode.js";
9
9
  import { Failed } from "../notice/Notice.js";
10
10
  import { ProgressBar } from "../value/ProgressBar.js";
11
11
  import { Button as AriaButton, DropZone as AriaDropZone, FileTrigger, } from "react-aria-components";
@@ -1,5 +1,5 @@
1
- import { type FieldIds } from "./context.js";
2
- import { type FormMode } from "./mode.js";
1
+ import { type FieldIds } from "../../foundations/form.js";
2
+ import { type FormMode } from "../../foundations/form.js";
3
3
  import { type FieldNote } from "./note.js";
4
4
  import { type ReactNode } from "react";
5
5
  export declare function Field({ label, hint, error, required, children, htmlFor, layout, action, mode, note, copy: copyAs, }: {
@@ -3,12 +3,12 @@ import { jsxs as _jsxs, Fragment as _Fragment, jsx as _jsx } from "react/jsx-run
3
3
  import { AriaLocale } from "../../foundations/aria.js";
4
4
  import { useUiCopy } from "../../foundations/copy.js";
5
5
  import { byDensity, useDensity } from "../../foundations/density.js";
6
+ import { FieldIdsContext, useFieldIds, } from "../../foundations/form.js";
7
+ import { FormModeProvider, useFormMode, } from "../../foundations/form.js";
6
8
  import { focusRing, tv } from "../../foundations/variants.js";
7
9
  import { FieldGroup, listOptionStyles } from "./aria.js";
8
- import { FieldIdsContext, useFieldIds, } from "./context.js";
9
- import { FieldCopy, useFieldCopy } from "./copy.js";
10
+ import { FieldCopy, useFieldCopy } from "./clipboard.js";
10
11
  import { useSheetLayout } from "./layout.js";
11
- import { FormModeProvider, useFormMode, } from "./mode.js";
12
12
  import { describedByOf, NoteCard, NoteFlag, } from "./note.js";
13
13
  import { Hint } from "../hint/Hint.js";
14
14
  import { Popover } from "../overlay/Popover.js";
@@ -1,4 +1,4 @@
1
- import { type FormMode } from "./mode.js";
1
+ import { type FormMode } from "../../foundations/form.js";
2
2
  /**
3
3
  * A text with suggestions: the open field that proposes.
4
4
  *
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } 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 { FieldGroup, listOptionStyles } from "./aria.js";
5
- import { useFieldIds } from "./context.js";
6
7
  import { ReadValue } from "./Field.js";
7
- import { useFormMode } from "./mode.js";
8
8
  import { Popover } from "../overlay/Popover.js";
9
9
  import { ComboBox as AriaComboBox, Input, ListBox, ListBoxItem, useFilter, } from "react-aria-components";
10
10
  const MAX = 8;
@@ -1,9 +1,7 @@
1
1
  export { Checkbox, type CheckItem, CheckList } from "./Checks.js";
2
- export { type FieldIds, useFieldIds } from "./context.js";
3
- export { FieldCopyProvider, shownText, useFieldCopy, } from "./copy.js";
2
+ export { FieldCopyProvider, shownText, useFieldCopy, } from "./clipboard.js";
4
3
  export { DropZone, type DropZoneProps } from "./DropZone.js";
5
4
  export { controlClasses, DataField, Field, NumberInput, ReadValue, Select, type SelectOption, TextArea, TextInput, } from "./Field.js";
6
5
  export { type FieldLayout, FieldLayoutProvider, useSheetLayout, } from "./layout.js";
7
- export { type FormMode, FormModeProvider, useFormMode, } from "./mode.js";
8
6
  export { Suggest, type SuggestProps } from "./Suggest.js";
9
7
  export { type FieldNote } from "./note.js";
@@ -1,15 +1,15 @@
1
- /* THE CONTROLS a form is written with.
1
+ /* THE CONTROLS a form is written with. The contract they speak with their `Field` —
2
+ `useFieldIds`, `useFormMode`— is in `foundations/form`: an application's own control
3
+ speaks it the same way.
2
4
  What ARRANGES them —the sheet, the section, the footer— lives in `components/form`: a
3
5
  control does not know a form exists, and is used the same way inside a cell, a filter or
4
6
  a dialog. */
5
7
  export { Checkbox, CheckList } from "./Checks.js";
6
- export { useFieldIds } from "./context.js";
7
8
  /* The copy button every field carries by its label — on by default, off by field or by form. */
8
- export { FieldCopyProvider, shownText, useFieldCopy, } from "./copy.js";
9
+ export { FieldCopyProvider, shownText, useFieldCopy, } from "./clipboard.js";
9
10
  export { DropZone } from "./DropZone.js";
10
11
  export { controlClasses, DataField, Field, NumberInput, ReadValue, Select, TextArea, TextInput, } from "./Field.js";
11
12
  export { FieldLayoutProvider, useSheetLayout, } from "./layout.js";
12
- export { FormModeProvider, useFormMode, } from "./mode.js";
13
13
  export { Suggest } from "./Suggest.js";
14
14
  /* A note that belongs to one field: a card in the record's margin, or a flag by its label. */
15
15
  export {} from "./note.js";
@@ -1,5 +1,5 @@
1
+ import { type FormMode } from "../../foundations/form.js";
1
2
  import type { OpenTarget } from "../../foundations/types.js";
2
- import { type FormMode } from "../field/mode.js";
3
3
  import type { LucideIcon } from "lucide-react";
4
4
  import { type ReactNode } from "react";
5
5
  export interface PickerItem {
@@ -2,20 +2,20 @@
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { AriaLocale } from "../../foundations/aria.js";
4
4
  import { fillCopy, useUiCopy } from "../../foundations/copy.js";
5
+ import { useFieldIds } from "../../foundations/form.js";
6
+ import { useFormMode } from "../../foundations/form.js";
5
7
  import { matchedWord, matches } from "../../foundations/match.js";
6
8
  import { cn, tv } from "../../foundations/variants.js";
7
9
  import { IconButton } from "../button/Button.js";
8
10
  import { FieldButton, FieldGroup } from "../field/aria.js";
9
- import { useFieldIds } from "../field/context.js";
10
11
  import { ReadValue } from "../field/Field.js";
11
- import { useFormMode } from "../field/mode.js";
12
12
  import { Menu } from "../menu/Menu.js";
13
13
  import { Popover } from "../overlay/Popover.js";
14
14
  import { MatchedText } from "./MatchedText.js";
15
15
  import { Avatar } from "../value/Person.js";
16
16
  import { AppWindow, Check, ChevronDown, Ellipsis, Eye, PanelRight, PictureInPicture2, SquareArrowOutUpRight, } from "lucide-react";
17
17
  import { useContext, useMemo, useRef, useState } from "react";
18
- import { Collection, ComboBox as AriaComboBox, ComboBoxStateContext, Header, Input, ListBox, ListBoxItem, ListBoxLoadMoreItem, ListBoxSection, Text, useFilter, } from "react-aria-components";
18
+ import { Collection, ComboBox as AriaComboBox, ComboBoxStateContext, Header, Input, ListBox, ListBoxItem, ListBoxLoadMoreItem, ListBoxSection, useFilter, } from "react-aria-components";
19
19
  /** The icon of each place a record opens — the same in every field and list. */
20
20
  const OPEN_ICON = {
21
21
  modal: PictureInPicture2,
@@ -271,7 +271,7 @@ function Row({ item, selected, marks, hints, query, locale, }) {
271
271
  item.haystack === undefined
272
272
  ? null
273
273
  : matchedWord(item.haystack, query, locale);
274
- return (_jsxs(_Fragment, { children: [marks ? (_jsx("span", { className: cn(hints ? "mt-0.5" : "", "flex size-(--size-icon-sm) shrink-0 items-center justify-center"), children: selected ? (_jsx(Check, { "aria-hidden": true, className: "size-(--size-icon-sm) text-primary forced-colors:text-[HighlightText]", strokeWidth: 2.5 })) : Icon !== undefined ? (_jsx(Icon, { "aria-hidden": true, className: "size-(--size-icon-sm) text-muted", strokeWidth: 1.75 })) : null })) : null, item.avatar === undefined ? null : (_jsx(Avatar, { name: item.name, size: "sm", ...(typeof item.avatar === "string" ? { src: item.avatar } : {}) })), _jsxs("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [_jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [_jsx(Text, { slot: "label", className: cn("min-w-0 truncate text-meta text-ink", selected ? "font-medium" : ""), children: _jsx(MatchedText, { text: item.name, query: query, locale: locale }) }), item.tags] }), item.meta === undefined ? null : (_jsx(Text, { slot: "description", className: "truncate text-micro tabular-nums text-muted", children: _jsx(MatchedText, { text: item.meta, query: query, locale: locale }) })), hidden === null ? null : (_jsxs("span", { className: "truncate text-micro text-muted", children: [copy.picker.matched, " ", _jsx("span", { className: "font-mono", children: _jsx(MatchedText, { text: hidden, query: query, locale: locale }) })] }))] }), item.aside === undefined ? null : (_jsx("span", { className: "flex shrink-0 items-center gap-1.5 self-center text-meta text-ink tabular-nums", children: item.aside })), !marks && selected ? (_jsx(Check, { "aria-hidden": true, className: "size-(--size-icon-sm) shrink-0 text-primary forced-colors:text-[HighlightText]", strokeWidth: 2.5 })) : null] }));
274
+ return (_jsxs(_Fragment, { children: [marks ? (_jsx("span", { className: cn(hints ? "mt-0.5" : "", "flex size-(--size-icon-sm) shrink-0 items-center justify-center"), children: selected ? (_jsx(Check, { "aria-hidden": true, className: "size-(--size-icon-sm) text-primary forced-colors:text-[HighlightText]", strokeWidth: 2.5 })) : Icon !== undefined ? (_jsx(Icon, { "aria-hidden": true, className: "size-(--size-icon-sm) text-muted", strokeWidth: 1.75 })) : null })) : null, item.avatar === undefined ? null : (_jsx(Avatar, { name: item.name, size: "sm", ...(typeof item.avatar === "string" ? { src: item.avatar } : {}) })), _jsxs("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [_jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [_jsx("span", { className: cn("min-w-0 truncate text-meta text-ink", selected ? "font-medium" : ""), children: _jsx(MatchedText, { text: item.name, query: query, locale: locale }) }), item.tags] }), item.meta === undefined && hidden === null ? null : (_jsxs("span", { className: "flex min-w-0 flex-col gap-0.5", children: [item.meta === undefined ? null : (_jsx("span", { className: "truncate text-micro tabular-nums text-muted", children: _jsx(MatchedText, { text: item.meta, query: query, locale: locale }) })), hidden === null ? null : (_jsxs("span", { className: "truncate text-micro text-muted", children: [copy.picker.matched, " ", _jsx("span", { className: "font-mono", children: _jsx(MatchedText, { text: hidden, query: query, locale: locale }) })] }))] }))] }), item.aside === undefined ? null : (_jsx("span", { className: "flex shrink-0 items-center gap-1.5 self-center text-meta text-ink tabular-nums", children: item.aside })), !marks && selected ? (_jsx(Check, { "aria-hidden": true, className: "size-(--size-icon-sm) shrink-0 text-primary forced-colors:text-[HighlightText]", strokeWidth: 2.5 })) : null] }));
275
275
  }
276
276
  /** Hands the combobox's `close` to the code outside it: React Aria owns the open state and
277
277
  * offers no controlled `isOpen`, and the «Buscar más…» row has to close the list it lives in. */
@@ -1,6 +1,6 @@
1
+ import { type FormMode } from "../../foundations/form.js";
1
2
  import type { OpenTarget } from "../../foundations/types.js";
2
- import { type FormMode } from "../field/mode.js";
3
- import { type TagOption } from "./choices.js";
3
+ import { type TagOption } from "./tagChoices.js";
4
4
  export type TagsFieldProps = {
5
5
  /** The ids of the chosen tags, in the order they were added. */
6
6
  value: readonly string[];
@@ -2,15 +2,15 @@
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { AriaLocale } from "../../foundations/aria.js";
4
4
  import { fillCopy, useUiCopy } from "../../foundations/copy.js";
5
+ import { useFieldIds } from "../../foundations/form.js";
6
+ import { useFormMode } from "../../foundations/form.js";
5
7
  import { cn } from "../../foundations/variants.js";
6
8
  import { fieldGroupStyles, listOptionStyles } from "../field/aria.js";
7
- import { useFieldIds } from "../field/context.js";
8
9
  import { ReadValue } from "../field/Field.js";
9
- import { useFormMode } from "../field/mode.js";
10
10
  import { Popover } from "../overlay/Popover.js";
11
- import { MatchedText } from "../picker/MatchedText.js";
12
- import { tagChoices, withoutTag, withTag, } from "./choices.js";
13
- import { Tags } from "./Tags.js";
11
+ import { MatchedText } from "./MatchedText.js";
12
+ import { tagChoices, withoutTag, withTag, } from "./tagChoices.js";
13
+ import { Tags } from "../tag/Tags.js";
14
14
  import { Avatar } from "../value/Person.js";
15
15
  import { Plus, Search } from "lucide-react";
16
16
  import { useContext, useEffect, useMemo, useRef, useState, } from "react";
@@ -1 +1,3 @@
1
1
  export { Picker, type PickerItem, type PickerProps, } from "./Picker.js";
2
+ export { type TagOption } from "./tagChoices.js";
3
+ export { TagsField, type TagsFieldProps } from "./TagsField.js";
@@ -1,2 +1,6 @@
1
- /* The replacement for `<select>` when you have to SEE in order to choose. */
1
+ /* CHOOSING RECORDS — one (`Picker`, the many2one) or several (`TagsField`, the many2many).
2
+ The replacement for `<select>` when you have to SEE in order to choose. */
2
3
  export { Picker, } from "./Picker.js";
4
+ /* El many2many: las fichas elegidas más una entrada que agrega la siguiente. */
5
+ export {} from "./tagChoices.js";
6
+ export { TagsField } from "./TagsField.js";
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { fillCopy, useUiCopy } from "../../foundations/copy.js";
4
- import { shownText } from "../field/copy.js";
4
+ import { shownText } from "../field/clipboard.js";
5
5
  import { CopyButton } from "../value/Copyable.js";
6
6
  import { useRef } from "react";
7
7
  /**
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { cn, focusRing } from "../../foundations/variants.js";
4
4
  import { Popover } from "../overlay/Popover.js";
5
5
  import { Pencil } from "lucide-react";
6
- import { useEffect, useId, useRef } from "react";
6
+ import { useEffect, useRef } from "react";
7
7
  import { Button as AriaButton, Dialog, DialogTrigger } from "react-aria-components";
8
8
  /* ==================================================================== *
9
9
  * CELL EDITOR — change one value of a list without leaving the list.
@@ -75,9 +75,7 @@ function EditorDialog({ label, editor, }) {
75
75
  * Internal to the table family: a column asks for it with `DataColumn.edit`.
76
76
  */
77
77
  export function CellEditor({ label, numeric, editor, children, }) {
78
- const verb = useId();
79
- const shown = useId();
80
78
  return (
81
79
  /* The row may open its record on click; changing one cell must not also open it. */
82
- _jsx("div", { onClick: (event) => event.stopPropagation(), className: "-mx-1 min-w-0", children: _jsxs(DialogTrigger, { children: [_jsxs(AriaButton, { "aria-labelledby": `${verb} ${shown}`, className: ({ isFocusVisible }) => cn("group/edit flex w-full min-w-0 cursor-pointer items-center gap-1.5 rounded-mark px-1 text-inherit outline-0 transition-colors duration-(--duration-instant) hover:bg-ink/6 pressed:bg-ink/10 aria-expanded:bg-primary/8", numeric ? "flex-row-reverse text-right" : "text-left", focusRing({ isFocusVisible })), children: [_jsx("span", { id: verb, hidden: true, children: label }), _jsx("span", { id: shown, className: "min-w-0 flex-1 truncate", children: children }), _jsx(Pencil, { "aria-hidden": true, strokeWidth: 2, className: "size-(--size-icon-sm) shrink-0 text-muted opacity-0 transition-opacity duration-(--duration-instant) group-hover/edit:opacity-100 group-focus-visible/edit:opacity-100 group-aria-expanded/edit:opacity-100" })] }), _jsx(Popover, { placement: numeric ? "bottom end" : "bottom start", children: _jsx(EditorDialog, { label: label, editor: editor }) })] }) }));
80
+ _jsx("div", { onClick: (event) => event.stopPropagation(), className: "-mx-1 min-w-0", children: _jsxs(DialogTrigger, { children: [_jsxs(AriaButton, { className: ({ isFocusVisible }) => cn("group/edit flex w-full min-w-0 cursor-pointer items-center gap-1.5 rounded-mark px-1 text-inherit outline-0 transition-colors duration-(--duration-instant) hover:bg-ink/6 pressed:bg-ink/10 aria-expanded:bg-primary/8", numeric ? "flex-row-reverse text-right" : "text-left", focusRing({ isFocusVisible })), children: [_jsx("span", { className: "sr-only", children: `${label} ` }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: children }), _jsx(Pencil, { "aria-hidden": true, strokeWidth: 2, className: "size-(--size-icon-sm) shrink-0 text-muted opacity-0 transition-opacity duration-(--duration-instant) group-hover/edit:opacity-100 group-focus-visible/edit:opacity-100 group-aria-expanded/edit:opacity-100" })] }), _jsx(Popover, { placement: numeric ? "bottom end" : "bottom start", children: _jsx(EditorDialog, { label: label, editor: editor }) })] }) }));
83
81
  }
@@ -1,3 +1,4 @@
1
+ import { type Pile } from "../../foundations/grouping.js";
1
2
  import type { Tone } from "../../foundations/types.js";
2
3
  import { type RowAction } from "./RowActions.js";
3
4
  import { type SortState } from "./Table.js";
@@ -148,7 +149,7 @@ export type GroupColumn<T> = {
148
149
  /** Cómo se lee esa clave. */
149
150
  labelOf: (key: string) => string;
150
151
  };
151
- export declare function DataTable<T extends object>({ data, columns, getRowId, sort, onSortChange, levels, selected, onSelectedChange, height, onRowClick, rowActions, totals, collapsed, onGroupToggle, create, copyable, }: {
152
+ export declare function DataTable<T extends object>({ data, columns, getRowId, sort, onSortChange, levels, selected, onSelectedChange, height, onRowClick, rowActions, totals, collapsed, onGroupToggle, piles, onMore, create, copyable, }: {
152
153
  data: readonly T[];
153
154
  columns: readonly DataColumn<T>[];
154
155
  getRowId: (row: T) => string;
@@ -181,6 +182,15 @@ export declare function DataTable<T extends object>({ data, columns, getRowId, s
181
182
  * table does not reopen what rows just folded. */
182
183
  collapsed?: readonly string[];
183
184
  onGroupToggle?: (key: string) => void;
185
+ /**
186
+ * The piles ALREADY counted by whoever keeps the data — «187» over the whole set, not
187
+ * the 12 rows of the page. Given, the table draws them as they come (their order, their
188
+ * labels, their `detail`) instead of grouping `data` by `levels`.
189
+ */
190
+ piles?: readonly Pile<T>[];
191
+ /** A pile shows its first page and counts them all: this asks for the next ones, and
192
+ * the table offers it under each pile that holds fewer than it counts. */
193
+ onMore?: (pile: Pile<T>) => void;
184
194
  /** Write a new record as a row of the list. See the file's header. */
185
195
  create?: DataTableCreate<T>;
186
196
  /** The cells' copy buttons, on by default. `false` takes them off the whole table; a
@@ -1,11 +1,12 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { fillCopy, useUiCopy } from "../../foundations/copy.js";
4
- import { pathKey } from "../../foundations/grouping.js";
4
+ import { pathKey, pileItems } from "../../foundations/grouping.js";
5
5
  import { CellCopy } from "./CellCopy.js";
6
6
  import { CellEditor } from "./CellEditor.js";
7
7
  import { EditRow, firstControl } from "./EditableTable.js";
8
8
  import { RowActions } from "./RowActions.js";
9
+ import { useRowWindow } from "./rowWindow.js";
9
10
  import { SelectBox, Table, Td, Th, THead, Tr, } from "./Table.js";
10
11
  import { aggregationFns, columnGroupingFeature, createColumnHelper, createExpandedRowModel, createGroupedRowModel, createSortedRowModel, rowAggregationFeature, rowExpandingFeature, rowSortingFeature, sortFns, tableFeatures, useTable, } from "@tanstack/react-table";
11
12
  import { ChevronRight, Plus } from "lucide-react";
@@ -117,7 +118,47 @@ export function aggregateOf(column, rows) {
117
118
  return numbers.length;
118
119
  }
119
120
  }
120
- export function DataTable({ data, columns, getRowId, sort, onSortChange, levels, selected, onSelectedChange, height, onRowClick, rowActions, totals = false, collapsed, onGroupToggle, create, copyable = true, }) {
121
+ /** The piles someone else counted, as the body draws them: open ones with what is inside,
122
+ * the deepest with its records and, when it holds fewer than it counts, its «show more». */
123
+ function entriesOfPiles(piles, keyOf, collapsed, onToggle, onMore) {
124
+ return piles.flatMap((pile) => {
125
+ const key = pathKey(pile.path);
126
+ const open = collapsed?.includes(key) !== true;
127
+ const head = {
128
+ kind: "pile",
129
+ id: `\u0000pile${key}`,
130
+ depth: pile.depth,
131
+ level: pile.field,
132
+ label: pile.label,
133
+ count: pile.count,
134
+ detail: pile.detail,
135
+ open,
136
+ toggle: onToggle === undefined ? undefined : () => onToggle(key),
137
+ };
138
+ if (!open)
139
+ return [head];
140
+ if (pile.piles.length > 0) {
141
+ return [head, ...entriesOfPiles(pile.piles, keyOf, collapsed, onToggle, onMore)];
142
+ }
143
+ const left = pile.count - pile.items.length;
144
+ return [
145
+ head,
146
+ ...pile.items.map((item) => ({ kind: "row", id: keyOf(item), item })),
147
+ ...(left > 0 && onMore !== undefined
148
+ ? [
149
+ {
150
+ kind: "more",
151
+ id: `\u0000more${key}`,
152
+ depth: pile.depth,
153
+ left,
154
+ more: () => onMore(pile),
155
+ },
156
+ ]
157
+ : []),
158
+ ];
159
+ });
160
+ }
161
+ export function DataTable({ data, columns, getRowId, sort, onSortChange, levels, selected, onSelectedChange, height, onRowClick, rowActions, totals = false, collapsed, onGroupToggle, piles, onMore, create, copyable = true, }) {
121
162
  const copy = useUiCopy();
122
163
  const draft = create?.draft ?? null;
123
164
  const draftRow = useRef(null);
@@ -208,7 +249,8 @@ export function DataTable({ data, columns, getRowId, sort, onSortChange, levels,
208
249
  sorting: sort === null || sort === undefined
209
250
  ? []
210
251
  : [{ id: sort.key, desc: sort.direction === "desc" }],
211
- grouping: (levels ?? []).map((level) => level.id),
252
+ /* Counted elsewhere, the tree comes made: grouping the page again would undo it. */
253
+ grouping: piles === undefined ? (levels ?? []).map((level) => level.id) : [],
212
254
  },
213
255
  // Groups start open: grouping and then having to open each group is two
214
256
  // gestures for what the user asked for in one.
@@ -246,7 +288,7 @@ export function DataTable({ data, columns, getRowId, sort, onSortChange, levels,
246
288
  the same thing; with several, folding the outer pile has to take the inner
247
289
  ones with it — otherwise closing «Ferretería Sur» leaves its months on screen,
248
290
  orphaned under a header that says it is closed. */
249
- const drawn = collapsed === undefined
291
+ const shown = collapsed === undefined
250
292
  ? rows
251
293
  : rows.filter((row) => {
252
294
  let node = row.getParentRow();
@@ -257,14 +299,56 @@ export function DataTable({ data, columns, getRowId, sort, onSortChange, levels,
257
299
  }
258
300
  return true;
259
301
  });
302
+ /* ONE sequence for the body, whoever grouped: the piles counted elsewhere, or the tree
303
+ TanStack built from the page. Before, the table only knew the second — grouped on the
304
+ server, a list said «187» in rows and «12» in the table. */
305
+ const entries = piles !== undefined
306
+ ? entriesOfPiles(piles, getRowId, collapsed, onGroupToggle, onMore)
307
+ : shown.map((row) => {
308
+ if (row.subRows.length === 0)
309
+ return { kind: "row", id: row.id, item: row.original };
310
+ const path = pathOf(row);
311
+ const key = pathKey(path);
312
+ const depth = path.length - 1;
313
+ const level = levels?.find((one) => one.id === row.groupingColumnId);
314
+ /* La CLAVE del montón —la del motor— y el rótulo, que lo pone el nivel: son dos
315
+ textos y sólo el segundo cambia con el idioma. */
316
+ const value = path[depth] ?? key;
317
+ return {
318
+ kind: "pile",
319
+ id: row.id,
320
+ depth,
321
+ level: level?.label,
322
+ label: level === undefined ? value : level.labelOf(value),
323
+ /* Las FILAS de adentro, no los subgrupos: `subRows` de un montón con niveles
324
+ abajo son los montones del nivel siguiente, y contarlos decía «2» donde hay
325
+ cuarenta filas. */
326
+ count: row.getLeafRows().filter((leaf) => leaf.subRows.length === 0).length,
327
+ detail: undefined,
328
+ open: collapsed === undefined ? row.getIsExpanded() : !collapsed.includes(key),
329
+ toggle: () => onGroupToggle === undefined ? row.toggleExpanded() : onGroupToggle(key),
330
+ };
331
+ });
260
332
  const selecting = onSelectedChange !== undefined;
261
- const leaves = useMemo(() => rows.filter((r) => r.subRows.length === 0), [rows]);
333
+ const width = columns.length + (selecting ? 1 : 0) + (rowActions === undefined ? 0 : 1);
334
+ /* Past a few hundred rows only the ones in view are drawn — see `rowWindow.tsx`. It
335
+ windows what is DRAWN, pile headers included and folded rows left out: that is the
336
+ sequence a person scrolls through. */
337
+ const view = useRowWindow({
338
+ count: entries.length,
339
+ getKey: (i) => entries[i]?.id ?? String(i),
340
+ });
341
+ /* Every record the selection can reach — folded ones included: «select all» over a
342
+ grouped list means all of them, not the ones whose pile happens to be open. */
343
+ const leaves = useMemo(() => piles !== undefined
344
+ ? pileItems(piles).map(getRowId)
345
+ : rows.filter((r) => r.subRows.length === 0).map((r) => r.id), [piles, rows, getRowId]);
262
346
  /* The aggregates are computed by the component and not by TanStack: its
263
347
  `aggregationFn` lives in the GROUP model, and a footer exists precisely when
264
348
  there are no groups. */
265
349
  const footer = useMemo(() => (totals ? columns.map((c) => aggregateOf(c, data)) : null), [totals, columns, data]);
266
- const all = leaves.length > 0 && leaves.every((r) => selected?.has(r.id) === true);
267
- const some = leaves.some((r) => selected?.has(r.id) === true);
350
+ const all = leaves.length > 0 && leaves.every((id) => selected?.has(id) === true);
351
+ const some = leaves.some((id) => selected?.has(id) === true);
268
352
  function toggle(id, on) {
269
353
  const n = new Set(selected ?? []);
270
354
  if (on)
@@ -273,36 +357,51 @@ export function DataTable({ data, columns, getRowId, sort, onSortChange, levels,
273
357
  n.delete(id);
274
358
  onSelectedChange?.(n);
275
359
  }
276
- const grid = (_jsxs(Table, { height: height, children: [_jsxs(THead, { children: [selecting ? (_jsx(Th, { width: 36, children: _jsx(SelectBox, { label: copy.selection.all, checked: all, indeterminate: some, onChange: (v) => onSelectedChange?.(v ? new Set(leaves.map((r) => r.id)) : new Set()) }) })) : null, columns.map((c) => (_jsx(Th, { sortKey: c.sortable === false ? undefined : c.id, sort: sort, onSort: onSortChange, numeric: c.numeric, width: c.width, children: c.header }, c.id))), rowActions === undefined ? null : _jsx(Th, { width: 1, children: "" })] }), _jsxs("tbody", { children: [drawn.map((row) => {
277
- const isGroup = row.subRows.length > 0;
278
- if (isGroup) {
279
- const path = pathOf(row);
280
- const key = pathKey(path);
281
- const depth = path.length - 1;
282
- const id = row.groupingColumnId ?? undefined;
283
- const level = levels?.find((one) => one.id === id);
284
- /* La CLAVE del montón —la del motor— y el rótulo, que lo pone el
285
- nivel: son dos textos y sólo el segundo cambia con el idioma. */
286
- const value = path[depth] ?? key;
287
- const open = collapsed === undefined
288
- ? row.getIsExpanded()
289
- : collapsed.includes(key) !== true;
290
- return (
291
- /* The group row: it spans the whole width, says which value it
292
- groups by and how many are inside. The counter is what makes it
293
- useful without opening it. */
294
- _jsx("tr", { className: "border-b border-line bg-ground", children: _jsx("td", { colSpan: columns.length + (selecting ? 1 : 0) + (rowActions === undefined ? 0 : 1), className: "px-3 py-1.5", style: depth === 0
295
- ? undefined
296
- : { paddingLeft: `calc(var(--spacing-indent) * ${depth} + 0.75rem)` }, children: _jsxs("button", { type: "button", onClick: () => onGroupToggle === undefined ? row.toggleExpanded() : onGroupToggle(key), "aria-expanded": open, className: "inline-flex items-center gap-1.5 rounded-control text-meta font-medium text-ink transition-colors duration-(--duration-instant) hover:text-primary focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary", children: [_jsx(ChevronRight, { className: `size-(--size-icon-sm) text-muted transition-transform duration-(--duration-instant) ${open ? "rotate-90" : ""}`, strokeWidth: 2, "aria-hidden": true }), level === undefined ? null : (_jsx("span", { className: "text-micro font-normal text-muted/70", children: level.label })), _jsx("span", { children: level === undefined ? value : level.labelOf(value) }), _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: row.getLeafRows().length.toLocaleString(copy.locale) })] }) }) }, row.id));
360
+ const grid = (_jsxs(Table, { height: height, ...(view.on
361
+ ? {
362
+ rowCount: 1 + entries.length + (draft === null ? 0 : 1) + (totals ? 1 : 0),
363
+ }
364
+ : {}), children: [_jsxs(THead, { ...(view.on ? { rowIndex: 1 } : {}), children: [selecting ? (_jsx(Th, { width: 36, children: _jsx(SelectBox, { label: copy.selection.all, checked: all, indeterminate: some, onChange: (v) => onSelectedChange?.(v ? new Set(leaves) : new Set()) }) })) : null, columns.map((c) => (_jsx(Th, { sortKey: c.sortable === false ? undefined : c.id, sort: sort, onSort: onSortChange, numeric: c.numeric, width: c.width, children: c.header }, c.id))), rowActions === undefined ? null : (_jsx("td", { className: "w-px border-b border-line-strong" }))] }), _jsxs("tbody", { ref: view.body.ref, children: [view.rows.flatMap((slot, at) => {
365
+ const entry = entries[slot.index];
366
+ if (entry === undefined)
367
+ return [];
368
+ const windowed = view.windowed(slot.index);
369
+ /* What is not drawn between the previous drawn row and this one: the rows
370
+ above the view before the first, and the gap before a row kept only because
371
+ it has the focus. */
372
+ const gap = view.pad(at === 0 ? 0 : view.rows[at - 1].end, slot.start, width, `\u0000pad${slot.index}`);
373
+ const indent = entry.kind === "row" || entry.depth === 0
374
+ ? undefined
375
+ : { paddingLeft: `calc(var(--spacing-indent) * ${entry.depth} + 0.75rem)` };
376
+ if (entry.kind === "pile") {
377
+ const head = (_jsxs(_Fragment, { children: [entry.toggle === undefined ? null : (_jsx(ChevronRight, { className: `size-(--size-icon-sm) text-muted transition-transform duration-(--duration-instant) ${entry.open ? "rotate-90" : ""}`, strokeWidth: 2, "aria-hidden": true })), entry.level === undefined ? null : (_jsx("span", { className: "text-micro font-normal text-muted", children: entry.level })), _jsx("span", { children: entry.label }), _jsx("span", { className: "inline-flex shrink-0 items-center rounded-pill border border-line-strong bg-ink/5 px-2 py-0.5 text-micro tabular-nums text-muted", children: entry.count.toLocaleString(copy.locale) }), entry.detail === undefined ? null : (_jsx("span", { className: "text-micro font-normal tabular-nums text-muted", children: entry.detail }))] }));
378
+ return [
379
+ gap,
380
+ /* The pile's row: it spans the whole width, says which value it groups by
381
+ and how many are inside — what makes it useful without opening it. */
382
+ _jsx("tr", { ...windowed, className: "border-b border-line bg-ground", children: _jsx("td", { colSpan: width, className: "px-3 py-1.5", style: indent, children: entry.toggle === undefined ? (_jsx("span", { className: "inline-flex items-center gap-1.5 text-meta font-medium text-ink", children: head })) : (_jsx("button", { type: "button", onClick: entry.toggle, "aria-expanded": entry.open, className: "inline-flex items-center gap-1.5 rounded-control text-meta font-medium text-ink transition-colors duration-(--duration-instant) hover:text-primary focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary", children: head })) }) }, entry.id),
383
+ ];
384
+ }
385
+ if (entry.kind === "more") {
386
+ return [
387
+ gap,
388
+ _jsx("tr", { ...windowed, className: "border-b border-line", children: _jsx("td", { colSpan: width, className: "px-3 py-1", style: indent, children: _jsx("button", { type: "button", onClick: entry.more, 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, {
389
+ count: entry.left.toLocaleString(copy.locale),
390
+ }) }) }) }, entry.id),
391
+ ];
297
392
  }
298
- const original = row.original;
299
- return (_jsxs(Tr, { selected: selected?.has(row.id) === true, onClick: onRowClick === undefined ? undefined : () => onRowClick(original), children: [selecting ? (_jsx(Td, { children: _jsx(SelectBox, { label: copy.selection.row, checked: selected?.has(row.id) === true, onChange: (v) => toggle(row.id, v) }) })) : null, columns.map((c) => {
300
- const t = c.tone?.(original);
301
- const n = c.note?.(original);
302
- const edit = c.edit?.when?.(original) === false ? undefined : c.edit;
303
- return (_jsx(Td, { numeric: c.numeric, mono: c.mono === true, ...(t === undefined ? {} : { tone: t }), ...(n === undefined ? {} : { title: n }), children: edit !== undefined ? (_jsx(CellEditor, { label: fillCopy(copy.inlineEdit.editCell, { column: c.header }), numeric: c.numeric === true, editor: (done) => edit.render(original, done), children: drawCell(copy, c, original) })) : copyable && c.copy !== false ? (_jsx(CellCopy, { header: c.header, numeric: c.numeric === true, ...(c.copy === undefined ? {} : { value: c.copy(original) }), children: drawCell(copy, c, original) })) : (drawCell(copy, c, original)) }, c.id));
304
- }), rowActions === undefined ? null : (_jsx(RowActions, { actions: rowActions(original) }))] }, row.id));
305
- }), create === undefined || draft === null ? null : (_jsxs(EditRow, { as: "tr", rowId: "\\u0000draft", rowRef: (node) => {
393
+ const original = entry.item;
394
+ const id = entry.id;
395
+ return [
396
+ gap,
397
+ _jsxs(Tr, { ...(windowed === undefined ? {} : { windowed }), selected: selected?.has(id) === true, onClick: onRowClick === undefined ? undefined : () => onRowClick(original), children: [selecting ? (_jsx(Td, { children: _jsx(SelectBox, { label: copy.selection.row, checked: selected?.has(id) === true, onChange: (v) => toggle(id, v) }) })) : null, columns.map((c) => {
398
+ const t = c.tone?.(original);
399
+ const n = c.note?.(original);
400
+ const edit = c.edit?.when?.(original) === false ? undefined : c.edit;
401
+ return (_jsx(Td, { numeric: c.numeric, mono: c.mono === true, ...(t === undefined ? {} : { tone: t }), ...(n === undefined ? {} : { title: n }), children: edit !== undefined ? (_jsx(CellEditor, { label: fillCopy(copy.inlineEdit.editCell, { column: c.header }), numeric: c.numeric === true, editor: (done) => edit.render(original, done), children: drawCell(copy, c, original) })) : copyable && c.copy !== false ? (_jsx(CellCopy, { header: c.header, numeric: c.numeric === true, ...(c.copy === undefined ? {} : { value: c.copy(original) }), children: drawCell(copy, c, original) })) : (drawCell(copy, c, original)) }, c.id));
402
+ }), rowActions === undefined ? null : (_jsx(RowActions, { actions: rowActions(original) }))] }, id),
403
+ ];
404
+ }), view.tail(width), create === undefined || draft === null ? null : (_jsxs(EditRow, { as: "tr", rowId: "\\u0000draft", rowRef: (node) => {
306
405
  draftRow.current = node;
307
406
  }, editable: true, editing: true, tabbable: false, className: "group/row", onStart: () => { }, onCommit: saveDraft, onCancel: () => {
308
407
  backToAdd.current = true;
@@ -318,7 +417,7 @@ export function DataTable({ data, columns, getRowId, sort, onSortChange, levels,
318
417
  }, children: [selecting ? _jsx("td", { className: DRAFT_CELL }) : null, columns.map((c) => (_jsx("td", { ...(c.input === undefined ? {} : { "data-edit": "" }), className: `${DRAFT_CELL} ${c.numeric === true ? "text-right tabular-nums" : ""}`, children: c.input?.(draft, (next) => {
319
418
  touched.current = true;
320
419
  create.onChange(next);
321
- }) ?? null }, c.id))), rowActions === undefined ? null : _jsx("td", { className: DRAFT_CELL })] }))] }), footer === null ? null : (_jsx("tfoot", { className: "sticky bottom-0 z-10 bg-ground", children: _jsxs("tr", { className: "border-t border-line-strong", children: [selecting ? _jsx("td", {}) : null, columns.map((c, i) => {
420
+ }) ?? null }, c.id))), rowActions === undefined ? null : _jsx("td", { className: DRAFT_CELL })] }))] }), footer === null ? null : (_jsx("tfoot", { className: "sticky bottom-0 z-10 bg-ground", children: _jsxs("tr", { className: "border-t border-line-strong", "aria-rowindex": view.on ? 2 + entries.length + (draft === null ? 0 : 1) : undefined, children: [selecting ? _jsx("td", {}) : null, columns.map((c, i) => {
322
421
  const value = footer[i] ?? null;
323
422
  return (_jsx("td", { className: `px-3 py-1.5 text-meta font-semibold text-ink ${c.numeric === true ? "text-right tabular-nums" : ""}`, children: i === 0 && value === null ? (_jsxs("span", { className: "text-muted", children: [data.length.toLocaleString(copy.locale), " ", data.length === 1 ? copy.table.row : copy.table.rows] })) : value === null ? null : (aggregateLabel(copy, c, value)) }, c.id));
324
423
  }), rowActions === undefined ? null : _jsx("td", {})] }) }))] }));