@moneyforward/mfui-components 4.3.0 → 4.5.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 (73) hide show
  1. package/dist/src/Accordion/Accordion.d.ts +21 -1
  2. package/dist/src/Accordion/Accordion.js +28 -4
  3. package/dist/src/Accordion/Accordion.types.d.ts +21 -0
  4. package/dist/src/Breadcrumbs/Breadcrumbs.js +1 -1
  5. package/dist/src/CheckboxCard/CheckboxCard.js +3 -1
  6. package/dist/src/DataTable/DataTable.js +9 -2
  7. package/dist/src/DataTable/DataTable.types.d.ts +15 -0
  8. package/dist/src/DataTable/DataTableHeaderRow/DataTableHeaderRow.js +3 -1
  9. package/dist/src/DataTable/DataTableHeaderRow/DataTableHeaderRow.types.d.ts +19 -1
  10. package/dist/src/DataTable/DataTableRow/DataTableRow.js +4 -1
  11. package/dist/src/DataTable/DataTableRow/DataTableRow.types.d.ts +23 -1
  12. package/dist/src/DateTimeSelection/DatePicker/DatePickerDateCell/index.d.ts +1 -1
  13. package/dist/src/DateTimeSelection/DatePicker/DatePickerDateCell/index.js +0 -1
  14. package/dist/src/DateTimeSelection/shared/CalendarGrid/CalendarGrid.js +1 -3
  15. package/dist/src/DateTimeSelection/shared/CalendarGrid/utilities/computeDataForStyling.js +2 -4
  16. package/dist/src/Dialog/Dialog.d.ts +1 -1
  17. package/dist/src/Dialog/Dialog.types.d.ts +1 -1
  18. package/dist/src/FileDropZone/FileDropZone.d.ts +5 -1
  19. package/dist/src/FileDropZone/FileDropZone.js +26 -6
  20. package/dist/src/FileDropZone/FileDropZone.types.d.ts +10 -0
  21. package/dist/src/MainNavigation/BaseMainNavigation.d.ts +1 -1
  22. package/dist/src/MainNavigation/BaseMainNavigation.js +2 -2
  23. package/dist/src/MainNavigation/MainNavigation.js +2 -1
  24. package/dist/src/MainNavigation/MainNavigation.types.d.ts +9 -0
  25. package/dist/src/MainNavigation/NarrowViewportMainNavigation.js +2 -2
  26. package/dist/src/MultipleSelectBox/MultipleSelectBox.js +27 -18
  27. package/dist/src/MultipleSelectBox/hooks/useInitialFocusOnOptionPanelOpen.js +1 -1
  28. package/dist/src/NavigationList/NavigationListItem/NavigationListItem.d.ts +3 -0
  29. package/dist/src/NavigationList/NavigationListItem/NavigationListItem.js +6 -2
  30. package/dist/src/NavigationList/NavigationListItem/NavigationListItem.types.d.ts +2 -1
  31. package/dist/src/NumberBox/NumberBox.d.ts +5 -0
  32. package/dist/src/NumberBox/NumberBox.js +55 -3
  33. package/dist/src/NumberBox/NumberBox.types.d.ts +35 -0
  34. package/dist/src/Pagination/Pagination.js +4 -1
  35. package/dist/src/Popover/Popover.js +3 -3
  36. package/dist/src/RadioButtonCard/RadioButtonCard.js +3 -1
  37. package/dist/src/SelectBox/SelectBox.js +20 -11
  38. package/dist/src/SelectBox/utils/getFlatOptions.js +1 -1
  39. package/dist/src/SidePane/SidePane.js +87 -7
  40. package/dist/src/SidePane/SidePane.types.d.ts +39 -0
  41. package/dist/src/SidePane/hooks/useSidePaneStateController.js +3 -5
  42. package/dist/src/SplitView/utils/parseSize.d.ts +1 -1
  43. package/dist/src/SplitView/utils/parseSize.js +4 -2
  44. package/dist/src/SubNavigation/SubNavigation.d.ts +10 -1
  45. package/dist/src/SubNavigation/SubNavigation.js +14 -3
  46. package/dist/src/SubNavigation/SubNavigation.types.d.ts +27 -1
  47. package/dist/src/Tabs/TabContext.d.ts +3 -1
  48. package/dist/src/Tabs/TabContext.js +1 -0
  49. package/dist/src/Tabs/TabItem/TabItem.js +14 -3
  50. package/dist/src/Tabs/Tabs.js +5 -21
  51. package/dist/src/Tabs/hooks/useTabRegistry.d.ts +24 -0
  52. package/dist/src/Tabs/hooks/useTabRegistry.js +130 -0
  53. package/dist/src/ToggleSwitch/index.d.ts +1 -1
  54. package/dist/src/ToggleSwitch/index.js +0 -1
  55. package/dist/src/Tooltip/hooks/useOpenStateDelayed.js +2 -2
  56. package/dist/src/utilities/dom/containsAcrossShadowRoots.d.ts +21 -0
  57. package/dist/src/utilities/dom/containsAcrossShadowRoots.js +33 -0
  58. package/dist/src/utilities/dom/setInputFiles.d.ts +21 -0
  59. package/dist/src/utilities/dom/setInputFiles.js +41 -0
  60. package/dist/src/utilities/dom/toFileList.d.ts +20 -0
  61. package/dist/src/utilities/dom/toFileList.js +26 -0
  62. package/dist/src/utilities/dom/useFixedColumns.js +4 -2
  63. package/dist/src/utilities/dom/useFocusTrap.js +1 -1
  64. package/dist/src/utilities/dom/useOnClickOutside.js +3 -1
  65. package/dist/src/utilities/dom/useTableEdgeCellMarkers.d.ts +56 -0
  66. package/dist/src/utilities/dom/useTableEdgeCellMarkers.js +223 -0
  67. package/dist/styled-system/recipes/data-table-cell-slot-recipe.js +4 -2
  68. package/dist/styled-system/types/composition.d.ts +1 -1
  69. package/dist/styles.css +20 -4
  70. package/dist/tsconfig.build.tsbuildinfo +1 -1
  71. package/package.json +3 -3
  72. /package/dist/src/{MultipleSelectBox/utils → utilities/dom}/getDeepActiveElement.d.ts +0 -0
  73. /package/dist/src/{MultipleSelectBox/utils → utilities/dom}/getDeepActiveElement.js +0 -0
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Marks a cell that is `:first-child` in its row but does **not** render in the table's first
3
+ * grid column, because a `rowSpan` from an earlier row occupies that column.
4
+ *
5
+ * The value is always `"false"`: the attribute only ever cancels the table-edge affordance, it
6
+ * never grants one. A cell that genuinely sits at the edge carries no attribute at all, so the
7
+ * class list of every cell in a table without `rowSpan` stays untouched.
8
+ *
9
+ * The DataTable cell recipes match this attribute, and the name has to be written there as a
10
+ * literal because PandaCSS extracts selector keys statically and cannot resolve an imported
11
+ * constant. `useTableEdgeCellMarkers.test.tsx` asserts that the two sides stay in sync.
12
+ */
13
+ export declare const TABLE_START_EDGE_ATTRIBUTE = "data-mfui-table-start-edge";
14
+ /**
15
+ * The `:last-child` mirror of {@link TABLE_START_EDGE_ATTRIBUTE}: the cell is last in its row but
16
+ * the table has further grid columns that a `rowSpan` from an earlier row occupies.
17
+ */
18
+ export declare const TABLE_END_EDGE_ATTRIBUTE = "data-mfui-table-end-edge";
19
+ /** The only value either marker attribute is ever set to. */
20
+ export declare const TABLE_EDGE_MARKER_VALUE = "false";
21
+ type TableEdgeAttribute = typeof TABLE_START_EDGE_ATTRIBUTE | typeof TABLE_END_EDGE_ATTRIBUTE;
22
+ type DisplacedEdgeCell = {
23
+ cell: HTMLTableCellElement;
24
+ attribute: TableEdgeAttribute;
25
+ };
26
+ /**
27
+ * Finds the cells whose position in source order disagrees with their rendered grid column.
28
+ *
29
+ * Each row group is walked into an occupancy grid, so that every cell gets its real `colStart` /
30
+ * `colEnd` rather than its `cellIndex`. A row's first cell is displaced when it starts after
31
+ * column 0, and its last cell is displaced when it ends before the table's last column. The
32
+ * table's column count is the widest row across all row groups, since the header and the body
33
+ * share the same columns.
34
+ *
35
+ * @param table - The `table` element to analyse.
36
+ *
37
+ * @returns One entry per displaced cell edge, in document order.
38
+ */
39
+ export declare const findDisplacedEdgeCells: (table: HTMLTableElement) => DisplacedEdgeCell[];
40
+ /**
41
+ * Marks the cells of a table whose source-order position disagrees with their rendered grid
42
+ * column, so that CSS can cancel the table-edge spacing `:first-child` / `:last-child` would
43
+ * otherwise give them.
44
+ *
45
+ * Markers are recomputed on mount, on every structural mutation inside the table, and cleared on
46
+ * unmount. They are deliberately not tied to any component re-rendering: memoized rows,
47
+ * key-recreated cells, virtualization and consumer state owned below the table all still produce
48
+ * correct markers, because each of them necessarily mutates the table DOM.
49
+ *
50
+ * Server-rendered HTML carries no markers, so a table using `rowSpan` keeps the misaligned
51
+ * spacing until hydration.
52
+ *
53
+ * @param tableRef - A ref to the `table` element to keep marked.
54
+ */
55
+ export declare const useTableEdgeCellMarkers: (tableRef: React.RefObject<HTMLTableElement | null>) => void;
56
+ export {};
@@ -0,0 +1,223 @@
1
+ import { useRef } from 'react';
2
+ import { useIsomorphicLayoutEffect } from '../react/useIsomorphicLayoutEffect';
3
+ /**
4
+ * Marks a cell that is `:first-child` in its row but does **not** render in the table's first
5
+ * grid column, because a `rowSpan` from an earlier row occupies that column.
6
+ *
7
+ * The value is always `"false"`: the attribute only ever cancels the table-edge affordance, it
8
+ * never grants one. A cell that genuinely sits at the edge carries no attribute at all, so the
9
+ * class list of every cell in a table without `rowSpan` stays untouched.
10
+ *
11
+ * The DataTable cell recipes match this attribute, and the name has to be written there as a
12
+ * literal because PandaCSS extracts selector keys statically and cannot resolve an imported
13
+ * constant. `useTableEdgeCellMarkers.test.tsx` asserts that the two sides stay in sync.
14
+ */
15
+ export const TABLE_START_EDGE_ATTRIBUTE = 'data-mfui-table-start-edge';
16
+ /**
17
+ * The `:last-child` mirror of {@link TABLE_START_EDGE_ATTRIBUTE}: the cell is last in its row but
18
+ * the table has further grid columns that a `rowSpan` from an earlier row occupies.
19
+ */
20
+ export const TABLE_END_EDGE_ATTRIBUTE = 'data-mfui-table-end-edge';
21
+ /** The only value either marker attribute is ever set to. */
22
+ export const TABLE_EDGE_MARKER_VALUE = 'false';
23
+ /** Mutation targets that can change the grid. Anything inside a cell cannot. */
24
+ const STRUCTURAL_TAG_NAMES = new Set(['TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR']);
25
+ /**
26
+ * The row groups of a table, each as its own list of rows.
27
+ *
28
+ * A `rowspan` is clamped to its own row group, so the occupancy grid is built per group. These
29
+ * collections only contain the table's own rows, which is what keeps a nested table invisible
30
+ * here — unlike `querySelectorAll('tr')`.
31
+ *
32
+ * @param table - The `table` element to read the row groups of.
33
+ *
34
+ * @returns One list of rows per row group, in document order.
35
+ */
36
+ const getSectionRows = (table) => {
37
+ const sections = [table.tHead, ...table.tBodies, table.tFoot].filter((section) => section != null);
38
+ if (sections.length > 0) {
39
+ return sections.map((section) => [...section.rows]);
40
+ }
41
+ // A `tr` written directly under `table` has no row group to belong to.
42
+ return table.rows.length > 0 ? [[...table.rows]] : [];
43
+ };
44
+ /**
45
+ * Resolves the grid columns of one row group by walking it into an occupancy grid, so that the
46
+ * edge cells of every row are described by where they render rather than by their `cellIndex`.
47
+ *
48
+ * @param rows - The rows of a single row group, in document order.
49
+ *
50
+ * @returns The edge cells and their grid columns, one entry per non-empty row.
51
+ */
52
+ const collectRowEdges = (rows) => {
53
+ // `occupied[rowIndex][column]` is true when a `rowSpan` from an earlier row reaches into it.
54
+ const occupied = rows.map(() => []);
55
+ const rowEdges = [];
56
+ rows.forEach((row, rowIndex) => {
57
+ const cells = [...row.cells];
58
+ const [firstCell] = cells;
59
+ const lastCell = cells.at(-1);
60
+ if (!firstCell || !lastCell)
61
+ return;
62
+ let column = 0;
63
+ let firstCellStart = 0;
64
+ let lastCellEnd = 0;
65
+ cells.forEach((cell, cellIndex) => {
66
+ while (occupied[rowIndex]?.[column] === true) {
67
+ column += 1;
68
+ }
69
+ const colSpan = Math.max(cell.colSpan, 1);
70
+ // `rowSpan={0}` means "every remaining row of this row group".
71
+ const rowSpan = cell.rowSpan === 0 ? rows.length - rowIndex : Math.max(cell.rowSpan, 1);
72
+ const columnEnd = column + colSpan;
73
+ const lastCoveredRow = Math.min(rowIndex + rowSpan, rows.length);
74
+ for (let coveredRow = rowIndex + 1; coveredRow < lastCoveredRow; coveredRow += 1) {
75
+ const occupiedColumns = occupied[coveredRow] ?? [];
76
+ for (let coveredColumn = column; coveredColumn < columnEnd; coveredColumn += 1) {
77
+ occupiedColumns[coveredColumn] = true;
78
+ }
79
+ }
80
+ if (cellIndex === 0) {
81
+ firstCellStart = column;
82
+ }
83
+ lastCellEnd = columnEnd;
84
+ column = columnEnd;
85
+ });
86
+ rowEdges.push({ firstCell, lastCell, start: firstCellStart, end: lastCellEnd });
87
+ });
88
+ return rowEdges;
89
+ };
90
+ /**
91
+ * Finds the cells whose position in source order disagrees with their rendered grid column.
92
+ *
93
+ * Each row group is walked into an occupancy grid, so that every cell gets its real `colStart` /
94
+ * `colEnd` rather than its `cellIndex`. A row's first cell is displaced when it starts after
95
+ * column 0, and its last cell is displaced when it ends before the table's last column. The
96
+ * table's column count is the widest row across all row groups, since the header and the body
97
+ * share the same columns.
98
+ *
99
+ * @param table - The `table` element to analyse.
100
+ *
101
+ * @returns One entry per displaced cell edge, in document order.
102
+ */
103
+ export const findDisplacedEdgeCells = (table) => {
104
+ const rowEdges = getSectionRows(table).flatMap(collectRowEdges);
105
+ const totalColumns = rowEdges.reduce((widest, { end }) => Math.max(widest, end), 0);
106
+ const displacedCells = [];
107
+ for (const { firstCell, lastCell, start, end } of rowEdges) {
108
+ if (start > 0) {
109
+ displacedCells.push({ cell: firstCell, attribute: TABLE_START_EDGE_ATTRIBUTE });
110
+ }
111
+ if (end < totalColumns) {
112
+ displacedCells.push({ cell: lastCell, attribute: TABLE_END_EDGE_ATTRIBUTE });
113
+ }
114
+ }
115
+ return displacedCells;
116
+ };
117
+ /**
118
+ * Whether any cell of this table carries a `rowspan` attribute.
119
+ *
120
+ * `colspan` is deliberately not probed. It is not that a `rowSpan` is the only way to displace a
121
+ * cell — a ragged table, whose rows disagree on total width, displaces its short rows' edge cells
122
+ * without any `rowSpan`, and {@link findDisplacedEdgeCells} resolves those correctly. Gating on
123
+ * `rowspan` is a scope decision: it is what guarantees that every table without one renders
124
+ * byte-identically to before this hook existed. Widen the gate if a ragged table is ever reported.
125
+ *
126
+ * @param table - The `table` element to probe.
127
+ *
128
+ * @returns Whether the table itself contains a `rowspan`.
129
+ */
130
+ const hasRowSpanAttribute = (table) => {
131
+ // The subtree query is asked first because it settles the common case — a table with no
132
+ // `rowSpan` anywhere, which is most of them — in a single pass. The answer is only ambiguous
133
+ // when it finds something, since the match may belong to a nested table.
134
+ if (table.querySelector('[rowspan]') === null)
135
+ return false;
136
+ // Without a nested table the query above cannot have reached outside this table, so it was
137
+ // already correctly scoped. Only a nested table forces the explicit walk over our own rows.
138
+ if (table.querySelector('table') === null)
139
+ return true;
140
+ return getSectionRows(table).some((rows) => rows.some((row) => [...row.cells].some((cell) => cell.hasAttribute('rowspan'))));
141
+ };
142
+ /**
143
+ * Whether a mutation record can have changed the grid of this table.
144
+ *
145
+ * @param record - The record to classify.
146
+ * @param table - The `table` element being observed.
147
+ *
148
+ * @returns Whether the markers have to be recomputed because of this record.
149
+ */
150
+ const isGridMutation = (record, table) => {
151
+ const { target } = record;
152
+ if (!(target instanceof Element))
153
+ return false;
154
+ // A nested table's mutations cannot change this table's grid.
155
+ if (target.closest('table') !== table)
156
+ return false;
157
+ // An attribute record targets the cell whose `rowspan` / `colspan` changed.
158
+ if (record.type === 'attributes')
159
+ return true;
160
+ // Rows and cells are added, removed and reordered on a row, a row group or the table itself.
161
+ // A `childList` record on a cell is content churn — a skeleton swap, a checkbox toggle, an
162
+ // inline edit — and cannot move any cell to a different column.
163
+ return STRUCTURAL_TAG_NAMES.has(target.tagName);
164
+ };
165
+ /**
166
+ * Marks the cells of a table whose source-order position disagrees with their rendered grid
167
+ * column, so that CSS can cancel the table-edge spacing `:first-child` / `:last-child` would
168
+ * otherwise give them.
169
+ *
170
+ * Markers are recomputed on mount, on every structural mutation inside the table, and cleared on
171
+ * unmount. They are deliberately not tied to any component re-rendering: memoized rows,
172
+ * key-recreated cells, virtualization and consumer state owned below the table all still produce
173
+ * correct markers, because each of them necessarily mutates the table DOM.
174
+ *
175
+ * Server-rendered HTML carries no markers, so a table using `rowSpan` keeps the misaligned
176
+ * spacing until hydration.
177
+ *
178
+ * @param tableRef - A ref to the `table` element to keep marked.
179
+ */
180
+ export const useTableEdgeCellMarkers = (tableRef) => {
181
+ const markedCellsRef = useRef([]);
182
+ useIsomorphicLayoutEffect(() => {
183
+ const table = tableRef.current;
184
+ if (!table)
185
+ return;
186
+ const clearMarkers = () => {
187
+ markedCellsRef.current.forEach(({ cell, attribute }) => {
188
+ cell.removeAttribute(attribute);
189
+ });
190
+ markedCellsRef.current = [];
191
+ };
192
+ const updateMarkers = () => {
193
+ // Recompute when the table has a `rowSpan`, or when markers are still set from an earlier
194
+ // run — the second half is what clears them once the last `rowSpan` is removed.
195
+ if (markedCellsRef.current.length === 0 && !hasRowSpanAttribute(table))
196
+ return;
197
+ clearMarkers();
198
+ const displacedCells = findDisplacedEdgeCells(table);
199
+ displacedCells.forEach(({ cell, attribute }) => {
200
+ cell.setAttribute(attribute, TABLE_EDGE_MARKER_VALUE);
201
+ });
202
+ markedCellsRef.current = displacedCells;
203
+ };
204
+ updateMarkers();
205
+ const observer = new MutationObserver((records) => {
206
+ if (!records.some((record) => isGridMutation(record, table)))
207
+ return;
208
+ updateMarkers();
209
+ });
210
+ observer.observe(table, {
211
+ childList: true,
212
+ subtree: true,
213
+ attributes: true,
214
+ // Our own `data-mfui-*` writes are excluded by the filter, so the observer cannot
215
+ // retrigger itself.
216
+ attributeFilter: ['rowspan', 'colspan'],
217
+ });
218
+ return () => {
219
+ observer.disconnect();
220
+ clearMarkers();
221
+ };
222
+ }, [tableRef]);
223
+ };
@@ -35,8 +35,10 @@ const dataTableCellSlotRecipeCompoundVariants = [
35
35
  "layout": "default",
36
36
  "css": {
37
37
  "root": {
38
- "tr:last-child > &::before": {
39
- "height": "[100%]"
38
+ "tr:last-child > &": {
39
+ "_before": {
40
+ "height": "[100%]"
41
+ }
40
42
  }
41
43
  }
42
44
  }
@@ -38,7 +38,7 @@ type TextStyleProperty =
38
38
  | 'fontVariationSettings'
39
39
  | 'fontWeight'
40
40
  | 'hangingPunctuation'
41
- | 'hypens'
41
+ | 'hyphens'
42
42
  | 'hyphenateCharacter'
43
43
  | 'hyphenateLimitChars'
44
44
  | 'letterSpacing'
package/dist/styles.css CHANGED
@@ -2977,6 +2977,14 @@
2977
2977
  padding-right: var(--mfui-spacing-mfui\.size\.padding\.container\.horizontal\.comfort);
2978
2978
  }
2979
2979
 
2980
+ .mfui-dDOVnx:first-child[data-mfui-table-start-edge="false"] {
2981
+ padding-left: 0;
2982
+ }
2983
+
2984
+ .mfui-dDOVnx:last-child[data-mfui-table-end-edge="false"] {
2985
+ padding-right: 0;
2986
+ }
2987
+
2980
2988
  .mfui-hYiFZu,.mfui-fUSGoR {
2981
2989
  display: flex;
2982
2990
  align-items: flex-start;
@@ -3066,6 +3074,14 @@
3066
3074
  padding-right: var(--mfui-spacing-mfui\.size\.padding\.container\.horizontal\.comfort);
3067
3075
  }
3068
3076
 
3077
+ .mfui-jMpBJi:first-child[data-mfui-table-start-edge="false"] {
3078
+ padding-left: 0;
3079
+ }
3080
+
3081
+ .mfui-jMpBJi:last-child[data-mfui-table-end-edge="false"] {
3082
+ padding-right: 0;
3083
+ }
3084
+
3069
3085
  .mfui-bfpAMt,.mfui-hdXyCU {
3070
3086
  display: flex;
3071
3087
  align-items: flex-start;
@@ -9228,10 +9244,6 @@ li:last-child > .mfui-cXGJls {
9228
9244
  cursor: not-allowed;
9229
9245
  }
9230
9246
 
9231
- tr:last-child > .mfui-gtOHny::before {
9232
- height: 100%;
9233
- }
9234
-
9235
9247
  .mfui-iHQAZp:is(:focus-visible, [data-focus-visible]) {
9236
9248
  border-color: var(--mfui-colors-mfui\.color\.signal-red\.border\.hovered);
9237
9249
  }
@@ -9320,6 +9332,10 @@ tr:last-child > .mfui-gtOHny::before {
9320
9332
  color: var(--mfui-colors-mfui\.color\.neutral\.content\.pressed);
9321
9333
  }
9322
9334
 
9335
+ tr:last-child > .mfui-juYiVQ::before {
9336
+ height: 100%;
9337
+ }
9338
+
9323
9339
  .mfui-iAQdxu:is(:hover, [data-hover]):not(:disabled, [disabled], [data-disabled]) {
9324
9340
  border-color: var(--mfui-colors-mfui\.color\.signal-red\.border\.hovered);
9325
9341
  }