@olenbetong/appframe-ds 1.8.0 → 1.9.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 (147) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/package.json +2 -2
  3. package/src/AfLookup/Lookup.css +9 -1
  4. package/src/AfLookup/Lookup.tsx +8 -10
  5. package/src/AfLookup/LookupCombobox.tsx +1 -9
  6. package/src/AfLookup/LookupEdit.tsx +3 -3
  7. package/src/AfLookup/LookupGrid.css +3 -2
  8. package/src/AfLookup/LookupGrid.tsx +8 -4
  9. package/src/autocomplete/Autocomplete.css +6 -17
  10. package/src/binding/BoundNumberInput.tsx +42 -42
  11. package/src/binding/BoundNumericTextField.tsx +102 -102
  12. package/src/binding/BoundTextField.tsx +49 -49
  13. package/src/binding/CancelButton.tsx +30 -30
  14. package/src/binding/CancelIconButton.tsx +30 -30
  15. package/src/binding/DataEditToolbar.css +23 -23
  16. package/src/binding/DataEditToolbar.tsx +84 -84
  17. package/src/binding/DeleteButton.tsx +35 -35
  18. package/src/binding/DeleteIconButton.tsx +35 -35
  19. package/src/binding/RefreshButton.tsx +31 -31
  20. package/src/binding/RefreshIconButton.tsx +33 -33
  21. package/src/binding/RefreshRowIconButton.tsx +33 -33
  22. package/src/binding/SaveButton.tsx +37 -37
  23. package/src/binding/SaveIconButton.tsx +38 -38
  24. package/src/binding/index.ts +20 -20
  25. package/src/container/Container.tsx +50 -50
  26. package/src/container/index.ts +1 -1
  27. package/src/cutoff/CutOffPicker.css +12 -12
  28. package/src/cutoff/CutOffPicker.tsx +165 -165
  29. package/src/cutoff/context.tsx +194 -194
  30. package/src/cutoff/dsCutOffDates.ts +21 -21
  31. package/src/cutoff/index.ts +4 -4
  32. package/src/cutoff/utils.test.ts +59 -59
  33. package/src/cutoff/utils.ts +276 -276
  34. package/src/filter/ChipListInput.tsx +79 -79
  35. package/src/filter/FieldFilterPanel.css +150 -150
  36. package/src/filter/FieldFilterPanel.tsx +303 -309
  37. package/src/filter/FilterBuilder.css +152 -152
  38. package/src/filter/FilterBuilder.test.tsx +153 -153
  39. package/src/filter/FilterBuilder.tsx +435 -435
  40. package/src/filter/FilterEditor.css +231 -231
  41. package/src/filter/FilterEditor.test.tsx +285 -285
  42. package/src/filter/FilterEditor.tsx +75 -75
  43. package/src/filter/FilterGroupEditor.tsx +160 -160
  44. package/src/filter/FilterNameDialog.css +22 -22
  45. package/src/filter/FilterNameDialog.tsx +81 -81
  46. package/src/filter/FilterRow.tsx +117 -117
  47. package/src/filter/FilterShareDialog.css +28 -28
  48. package/src/filter/FilterShareDialog.tsx +195 -201
  49. package/src/filter/FilterStringField.tsx +72 -78
  50. package/src/filter/FilterValueEditor.tsx +239 -239
  51. package/src/filter/SavedFilterTree.css +107 -107
  52. package/src/filter/SavedFilterTree.test.tsx +94 -94
  53. package/src/filter/SavedFilterTree.tsx +218 -218
  54. package/src/filter/fieldFilter.test.ts +158 -158
  55. package/src/filter/fieldFilter.ts +139 -139
  56. package/src/filter/index.ts +27 -27
  57. package/src/filter/types.ts +38 -38
  58. package/src/filter/useDistinctValues.test.ts +102 -102
  59. package/src/filter/useDistinctValues.ts +230 -230
  60. package/src/global.d.ts +11 -11
  61. package/src/grid/AfGridColumnsPanel.tsx +4 -7
  62. package/src/grid/AfGridError.tsx +2 -2
  63. package/src/grid/AfHeaderFilterCell.tsx +0 -3
  64. package/src/grid/AfHeaderFilterPanel.tsx +1 -3
  65. package/src/grid/GridEditLookup.tsx +0 -2
  66. package/src/grid/Toolbar.tsx +9 -38
  67. package/src/grid/index.css +44 -0
  68. package/src/grid/index.tsx +12 -3
  69. package/src/grid/slots/index.tsx +15 -40
  70. package/src/grid/useAfGridApi.ts +0 -1
  71. package/src/grid/useAfPersistedState.ts +4 -8
  72. package/src/grid/useAfSizeMode.ts +19 -0
  73. package/src/i18n.ts +13 -13
  74. package/src/index.ts +14 -14
  75. package/src/input/NumberInput.tsx +0 -2
  76. package/src/layout/AppMenu.tsx +74 -74
  77. package/src/layout/AppMenuDrawer.css +71 -71
  78. package/src/layout/AppMenuDrawer.tsx +122 -122
  79. package/src/layout/DialogMotion.css +79 -79
  80. package/src/layout/ErrorBoundary.tsx +162 -162
  81. package/src/layout/PageContainer.css +48 -48
  82. package/src/layout/PageContainer.tsx +41 -41
  83. package/src/layout/ResponsiveDialog.css +86 -86
  84. package/src/layout/ResponsiveDialog.tsx +55 -55
  85. package/src/layout/index.tsx +70 -70
  86. package/src/link/Link.tsx +23 -23
  87. package/src/link/index.ts +1 -1
  88. package/src/list/ItemList.css +2 -1
  89. package/src/mdi/Mdi.test.tsx +91 -91
  90. package/src/mdi/Mdi.tsx +141 -141
  91. package/src/mdi/MdiConfirmCloseDialog.tsx +41 -47
  92. package/src/mdi/MdiContext.tsx +59 -59
  93. package/src/mdi/MdiDocumentContext.tsx +60 -60
  94. package/src/mdi/MdiPanel.tsx +61 -61
  95. package/src/mdi/MdiRestore.test.tsx +86 -86
  96. package/src/mdi/MdiTab.tsx +30 -30
  97. package/src/mdi/controller.test.ts +382 -382
  98. package/src/mdi/controller.ts +656 -656
  99. package/src/mdi/dockviewHost.ts +132 -132
  100. package/src/mdi/fakeHost.ts +200 -200
  101. package/src/mdi/host.ts +44 -44
  102. package/src/mdi/ids.test.ts +20 -20
  103. package/src/mdi/ids.ts +18 -18
  104. package/src/mdi/index.ts +31 -31
  105. package/src/mdi/layoutRestore.test.ts +156 -156
  106. package/src/mdi/layoutRestore.ts +311 -311
  107. package/src/mdi/layoutSpec.test.ts +143 -143
  108. package/src/mdi/layoutSpec.ts +214 -214
  109. package/src/mdi/layoutStore.test.ts +93 -93
  110. package/src/mdi/layoutStore.ts +99 -99
  111. package/src/mdi/memorySettingsStore.ts +21 -21
  112. package/src/mdi/registry.ts +34 -34
  113. package/src/mdi/testTypes.ts +37 -37
  114. package/src/mdi/theme.ts +22 -22
  115. package/src/mdi/types.ts +214 -214
  116. package/src/page/Page.css +57 -57
  117. package/src/page/PageBlock.css +64 -64
  118. package/src/page/PageBlock.tsx +93 -93
  119. package/src/page/PageHeader.css +88 -88
  120. package/src/page/PageHeader.tsx +115 -115
  121. package/src/page/PageTitle.css +5 -5
  122. package/src/page/PageTitle.tsx +21 -21
  123. package/src/page/index.ts +4 -4
  124. package/src/paper/Paper.tsx +52 -52
  125. package/src/progress/LinearProgress.css +121 -121
  126. package/src/progress/LinearProgress.tsx +106 -106
  127. package/src/progress/index.ts +1 -1
  128. package/src/report-downloader/ReportDownloader.css +67 -67
  129. package/src/report-downloader/components/StatusItem.tsx +87 -87
  130. package/src/report-downloader/components/StatusList.tsx +28 -28
  131. package/src/report-downloader/index.ts +41 -41
  132. package/src/report-downloader/status.tsx +92 -92
  133. package/src/sortable/DropIndicator.css +51 -51
  134. package/src/sortable/DropIndicator.tsx +27 -27
  135. package/src/sortable/SortableItemList.css +21 -21
  136. package/src/sortable/SortableItemList.tsx +264 -264
  137. package/src/sortable/index.ts +8 -8
  138. package/src/sortable/reorderSortOrder.ts +126 -126
  139. package/src/sortable/sortOrder.ts +101 -101
  140. package/src/sortable/types.ts +9 -9
  141. package/src/sortable/useSortableContainer.ts +53 -53
  142. package/src/sortable/useSortableItem.ts +95 -95
  143. package/src/sortable/useSortableList.ts +117 -117
  144. package/src/test/happy-dom-document-class.ts +24 -24
  145. package/src/test/setup.ts +27 -27
  146. package/src/theme/sizeMode.ts +62 -0
  147. package/src/theme/useDsColorScheme.ts +53 -53
@@ -1,95 +1,95 @@
1
- import { combine } from "@atlaskit/pragmatic-drag-and-drop/utils/combine";
2
- import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/adapter/element-adapter";
3
- import {
4
- attachClosestEdge,
5
- type Edge,
6
- extractClosestEdge,
7
- } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
8
- import { type RefObject, useEffect, useState } from "react";
9
-
10
- import { isSortableDragData, type SortableDragData } from "./useSortableContainer.js";
11
- import type { SortableRecord } from "./types.js";
12
-
13
- export type SortableItemState =
14
- | { type: "idle" }
15
- | { type: "is-dragging" }
16
- | { type: "is-dragging-over"; edge: Edge | null };
17
-
18
- export interface UseSortableItemOptions<T extends SortableRecord> {
19
- /** Identifies the list. Must match the type given to the container/list */
20
- type: string;
21
- record: T;
22
- /** Element that acts as the drop target, usually the list item */
23
- itemRef: RefObject<HTMLElement | null>;
24
- /** Element that starts the drag, usually a drag handle button */
25
- handleRef: RefObject<HTMLElement | null>;
26
- enabled?: boolean;
27
- }
28
-
29
- /**
30
- * Makes a single list item draggable and turns it into a drop target that
31
- * reports which edge is closest to the pointer, so a drop indicator can be
32
- * rendered.
33
- */
34
- export function useSortableItem<T extends SortableRecord>({
35
- type,
36
- record,
37
- itemRef,
38
- handleRef,
39
- enabled = true,
40
- }: UseSortableItemOptions<T>): SortableItemState {
41
- let [state, setState] = useState<SortableItemState>({ type: "idle" });
42
-
43
- useEffect(() => {
44
- if (!enabled) return;
45
- let item = itemRef.current;
46
- let handle = handleRef.current;
47
- if (!item || !handle) return;
48
-
49
- return combine(
50
- draggable({
51
- element: handle,
52
- getInitialData: () => ({ type, record }) satisfies SortableDragData<T>,
53
- onDragStart: () => setState({ type: "is-dragging" }),
54
- onDrop: () => setState({ type: "idle" }),
55
- }),
56
- dropTargetForElements({
57
- element: item,
58
- canDrop({ source }) {
59
- if (!isSortableDragData<T>(source.data, type)) return false;
60
- return source.data.record.PrimKey !== record.PrimKey;
61
- },
62
- getData({ input }) {
63
- let data: SortableDragData<T> = { type, record };
64
- return attachClosestEdge(data, {
65
- element: item,
66
- input,
67
- allowedEdges: ["top", "bottom"],
68
- });
69
- },
70
- getIsSticky: () => true,
71
- onDragEnter({ self }) {
72
- let edge = extractClosestEdge(self.data);
73
- setState({ type: "is-dragging-over", edge });
74
- },
75
- onDrag({ self }) {
76
- let edge = extractClosestEdge(self.data);
77
- setState((current) => {
78
- if (current.type === "is-dragging-over" && current.edge === edge) {
79
- return current;
80
- }
81
- return { type: "is-dragging-over", edge };
82
- });
83
- },
84
- onDragLeave() {
85
- setState({ type: "idle" });
86
- },
87
- onDrop() {
88
- setState({ type: "idle" });
89
- },
90
- }),
91
- );
92
- }, [enabled, record, type, itemRef, handleRef]);
93
-
94
- return state;
95
- }
1
+ import { combine } from "@atlaskit/pragmatic-drag-and-drop/utils/combine";
2
+ import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/adapter/element-adapter";
3
+ import {
4
+ attachClosestEdge,
5
+ type Edge,
6
+ extractClosestEdge,
7
+ } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
8
+ import { type RefObject, useEffect, useState } from "react";
9
+
10
+ import { isSortableDragData, type SortableDragData } from "./useSortableContainer.js";
11
+ import type { SortableRecord } from "./types.js";
12
+
13
+ export type SortableItemState =
14
+ | { type: "idle" }
15
+ | { type: "is-dragging" }
16
+ | { type: "is-dragging-over"; edge: Edge | null };
17
+
18
+ export interface UseSortableItemOptions<T extends SortableRecord> {
19
+ /** Identifies the list. Must match the type given to the container/list */
20
+ type: string;
21
+ record: T;
22
+ /** Element that acts as the drop target, usually the list item */
23
+ itemRef: RefObject<HTMLElement | null>;
24
+ /** Element that starts the drag, usually a drag handle button */
25
+ handleRef: RefObject<HTMLElement | null>;
26
+ enabled?: boolean;
27
+ }
28
+
29
+ /**
30
+ * Makes a single list item draggable and turns it into a drop target that
31
+ * reports which edge is closest to the pointer, so a drop indicator can be
32
+ * rendered.
33
+ */
34
+ export function useSortableItem<T extends SortableRecord>({
35
+ type,
36
+ record,
37
+ itemRef,
38
+ handleRef,
39
+ enabled = true,
40
+ }: UseSortableItemOptions<T>): SortableItemState {
41
+ let [state, setState] = useState<SortableItemState>({ type: "idle" });
42
+
43
+ useEffect(() => {
44
+ if (!enabled) return;
45
+ let item = itemRef.current;
46
+ let handle = handleRef.current;
47
+ if (!item || !handle) return;
48
+
49
+ return combine(
50
+ draggable({
51
+ element: handle,
52
+ getInitialData: () => ({ type, record }) satisfies SortableDragData<T>,
53
+ onDragStart: () => setState({ type: "is-dragging" }),
54
+ onDrop: () => setState({ type: "idle" }),
55
+ }),
56
+ dropTargetForElements({
57
+ element: item,
58
+ canDrop({ source }) {
59
+ if (!isSortableDragData<T>(source.data, type)) return false;
60
+ return source.data.record.PrimKey !== record.PrimKey;
61
+ },
62
+ getData({ input }) {
63
+ let data: SortableDragData<T> = { type, record };
64
+ return attachClosestEdge(data, {
65
+ element: item,
66
+ input,
67
+ allowedEdges: ["top", "bottom"],
68
+ });
69
+ },
70
+ getIsSticky: () => true,
71
+ onDragEnter({ self }) {
72
+ let edge = extractClosestEdge(self.data);
73
+ setState({ type: "is-dragging-over", edge });
74
+ },
75
+ onDrag({ self }) {
76
+ let edge = extractClosestEdge(self.data);
77
+ setState((current) => {
78
+ if (current.type === "is-dragging-over" && current.edge === edge) {
79
+ return current;
80
+ }
81
+ return { type: "is-dragging-over", edge };
82
+ });
83
+ },
84
+ onDragLeave() {
85
+ setState({ type: "idle" });
86
+ },
87
+ onDrop() {
88
+ setState({ type: "idle" });
89
+ },
90
+ }),
91
+ );
92
+ }, [enabled, record, type, itemRef, handleRef]);
93
+
94
+ return state;
95
+ }
@@ -1,117 +1,117 @@
1
- import type { Edge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
2
- import type { DataObject } from "@olenbetong/appframe-data";
3
- import { useData } from "@olenbetong/appframe-react";
4
- import { useCallback, useMemo } from "react";
5
-
6
- import { reorderSortOrder } from "./reorderSortOrder.js";
7
- import { DEFAULT_SORT_ORDER_SPACING } from "./sortOrder.js";
8
- import type { SortableRecord } from "./types.js";
9
- import { useSortableContainer } from "./useSortableContainer.js";
10
-
11
- export interface SortableListOptions<T extends SortableRecord> {
12
- /** Data object holding the records to display */
13
- dataObject: DataObject<T>;
14
- /**
15
- * Data object used to persist the new sort order, when the list is bound to
16
- * a read-only view. Defaults to `dataObject`.
17
- */
18
- updateDataObject?: DataObject<any>;
19
- /**
20
- * Identifies the list. Only items sharing the same type can be dropped on
21
- * each other. Defaults to the data object id.
22
- */
23
- type?: string;
24
- /** Limits the list to a subset of the records, e.g. rows for the current master record */
25
- filter?: (record: T) => boolean;
26
- /** Disables dragging, e.g. while the list is read only */
27
- enabled?: boolean;
28
- /** Spacing used when appending to the end of the list */
29
- spacing?: number;
30
- /**
31
- * Called when the list runs out of free sort order values (two records share
32
- * the same sort order). Typically executes a stored procedure that renumbers
33
- * the list with even spacing.
34
- */
35
- onNormalize?: () => Promise<void>;
36
- onError?: (error: unknown) => void;
37
- }
38
-
39
- export interface SortableList<T extends SortableRecord> {
40
- /** Records passing `filter`, sorted by `SortOrder` */
41
- records: T[];
42
- /** Identifies the list. Pass to `useSortableItem` when rendering items manually */
43
- type: string;
44
- enabled: boolean;
45
- /**
46
- * Moves a record relative to another record. `edge` decides whether it is
47
- * placed before (`null`/`"top"`) or after (`"bottom"`) the target.
48
- */
49
- moveItem: (sourcePrimKey: string, targetPrimKey: string, edge: Edge | null) => Promise<void>;
50
- }
51
-
52
- /**
53
- * Keeps a data object sorted by its `SortOrder` column and handles drops from
54
- * the sortable items rendered inside it.
55
- *
56
- * Use {@link SortableItemList} for the common list layout, or this hook
57
- * directly together with {@link useSortableItem} and {@link DropIndicator}
58
- * when the items need custom markup.
59
- */
60
- export function useSortableList<T extends SortableRecord>({
61
- dataObject,
62
- updateDataObject,
63
- type,
64
- filter,
65
- enabled = true,
66
- spacing = DEFAULT_SORT_ORDER_SPACING,
67
- onNormalize,
68
- onError,
69
- }: SortableListOptions<T>): SortableList<T> {
70
- let data = useData(dataObject);
71
- let listType = type ?? dataObject.getDataSourceId();
72
- let dataObjectForUpdate = updateDataObject ?? dataObject;
73
- let records = useMemo(() => {
74
- let filtered = filter ? data.filter(filter) : data;
75
- return [...filtered].sort((a, b) => (a.SortOrder ?? spacing) - (b.SortOrder ?? spacing));
76
- }, [data, filter, spacing]);
77
-
78
- let moveItem = useCallback(
79
- async (sourcePrimKey: string, targetPrimKey: string, edge: Edge | null) => {
80
- try {
81
- await reorderSortOrder({
82
- records,
83
- sourcePrimKey,
84
- targetPrimKey,
85
- edge,
86
- spacing,
87
- onNormalize,
88
- refreshRecords: async () => {
89
- await dataObject.refreshDataSource();
90
- let refreshed = dataObject.getData();
91
- let filtered = filter ? refreshed.filter(filter) : refreshed;
92
- return [...filtered].sort((a, b) => (a.SortOrder ?? spacing) - (b.SortOrder ?? spacing));
93
- },
94
- listDataObject: dataObject,
95
- updateDataObject: dataObjectForUpdate,
96
- });
97
- } catch (error) {
98
- if (onError) {
99
- onError(error);
100
- } else {
101
- throw error;
102
- }
103
- }
104
- },
105
- [records, spacing, onNormalize, dataObject, dataObjectForUpdate, filter, onError],
106
- );
107
-
108
- useSortableContainer<T>({
109
- type: listType,
110
- enabled,
111
- onDrop: ({ source, target, edge }) => {
112
- void moveItem(source.PrimKey, target.PrimKey, edge);
113
- },
114
- });
115
-
116
- return { records, type: listType, enabled, moveItem };
117
- }
1
+ import type { Edge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
2
+ import type { DataObject } from "@olenbetong/appframe-data";
3
+ import { useData } from "@olenbetong/appframe-react";
4
+ import { useCallback, useMemo } from "react";
5
+
6
+ import { reorderSortOrder } from "./reorderSortOrder.js";
7
+ import { DEFAULT_SORT_ORDER_SPACING } from "./sortOrder.js";
8
+ import type { SortableRecord } from "./types.js";
9
+ import { useSortableContainer } from "./useSortableContainer.js";
10
+
11
+ export interface SortableListOptions<T extends SortableRecord> {
12
+ /** Data object holding the records to display */
13
+ dataObject: DataObject<T>;
14
+ /**
15
+ * Data object used to persist the new sort order, when the list is bound to
16
+ * a read-only view. Defaults to `dataObject`.
17
+ */
18
+ updateDataObject?: DataObject<any>;
19
+ /**
20
+ * Identifies the list. Only items sharing the same type can be dropped on
21
+ * each other. Defaults to the data object id.
22
+ */
23
+ type?: string;
24
+ /** Limits the list to a subset of the records, e.g. rows for the current master record */
25
+ filter?: (record: T) => boolean;
26
+ /** Disables dragging, e.g. while the list is read only */
27
+ enabled?: boolean;
28
+ /** Spacing used when appending to the end of the list */
29
+ spacing?: number;
30
+ /**
31
+ * Called when the list runs out of free sort order values (two records share
32
+ * the same sort order). Typically executes a stored procedure that renumbers
33
+ * the list with even spacing.
34
+ */
35
+ onNormalize?: () => Promise<void>;
36
+ onError?: (error: unknown) => void;
37
+ }
38
+
39
+ export interface SortableList<T extends SortableRecord> {
40
+ /** Records passing `filter`, sorted by `SortOrder` */
41
+ records: T[];
42
+ /** Identifies the list. Pass to `useSortableItem` when rendering items manually */
43
+ type: string;
44
+ enabled: boolean;
45
+ /**
46
+ * Moves a record relative to another record. `edge` decides whether it is
47
+ * placed before (`null`/`"top"`) or after (`"bottom"`) the target.
48
+ */
49
+ moveItem: (sourcePrimKey: string, targetPrimKey: string, edge: Edge | null) => Promise<void>;
50
+ }
51
+
52
+ /**
53
+ * Keeps a data object sorted by its `SortOrder` column and handles drops from
54
+ * the sortable items rendered inside it.
55
+ *
56
+ * Use {@link SortableItemList} for the common list layout, or this hook
57
+ * directly together with {@link useSortableItem} and {@link DropIndicator}
58
+ * when the items need custom markup.
59
+ */
60
+ export function useSortableList<T extends SortableRecord>({
61
+ dataObject,
62
+ updateDataObject,
63
+ type,
64
+ filter,
65
+ enabled = true,
66
+ spacing = DEFAULT_SORT_ORDER_SPACING,
67
+ onNormalize,
68
+ onError,
69
+ }: SortableListOptions<T>): SortableList<T> {
70
+ let data = useData(dataObject);
71
+ let listType = type ?? dataObject.getDataSourceId();
72
+ let dataObjectForUpdate = updateDataObject ?? dataObject;
73
+ let records = useMemo(() => {
74
+ let filtered = filter ? data.filter(filter) : data;
75
+ return [...filtered].sort((a, b) => (a.SortOrder ?? spacing) - (b.SortOrder ?? spacing));
76
+ }, [data, filter, spacing]);
77
+
78
+ let moveItem = useCallback(
79
+ async (sourcePrimKey: string, targetPrimKey: string, edge: Edge | null) => {
80
+ try {
81
+ await reorderSortOrder({
82
+ records,
83
+ sourcePrimKey,
84
+ targetPrimKey,
85
+ edge,
86
+ spacing,
87
+ onNormalize,
88
+ refreshRecords: async () => {
89
+ await dataObject.refreshDataSource();
90
+ let refreshed = dataObject.getData();
91
+ let filtered = filter ? refreshed.filter(filter) : refreshed;
92
+ return [...filtered].sort((a, b) => (a.SortOrder ?? spacing) - (b.SortOrder ?? spacing));
93
+ },
94
+ listDataObject: dataObject,
95
+ updateDataObject: dataObjectForUpdate,
96
+ });
97
+ } catch (error) {
98
+ if (onError) {
99
+ onError(error);
100
+ } else {
101
+ throw error;
102
+ }
103
+ }
104
+ },
105
+ [records, spacing, onNormalize, dataObject, dataObjectForUpdate, filter, onError],
106
+ );
107
+
108
+ useSortableContainer<T>({
109
+ type: listType,
110
+ enabled,
111
+ onDrop: ({ source, target, edge }) => {
112
+ void moveItem(source.PrimKey, target.PrimKey, edge);
113
+ },
114
+ });
115
+
116
+ return { records, type: listType, enabled, moveItem };
117
+ }
@@ -1,24 +1,24 @@
1
- /**
2
- * happy-dom exposes a `Document` constructor on the window that is *not* in the
3
- * prototype chain of the actual `document` (which is an `HTMLDocument` built on
4
- * happy-dom's internal `Document` class). `document instanceof Document` is
5
- * therefore `false`, which is enough to hang libraries that walk up the tree
6
- * until they reach the document — the `@oddbird/popover-polyfill` bundled into
7
- * `@digdir/designsystemet-web` loops forever in `nearestInclusiveOpenPopover`,
8
- * taking the whole worker with it.
9
- *
10
- * Point the global `Document` at the class the document really inherits from.
11
- * Must run before anything that captures `Document`.
12
- */
13
- export function fixHappyDomDocumentClass(): void {
14
- if (typeof document === "undefined" || document instanceof Document) return;
15
-
16
- for (let proto = Object.getPrototypeOf(document); proto; proto = Object.getPrototypeOf(proto)) {
17
- let ctor = proto.constructor;
18
- if (ctor?.name === "Document") {
19
- globalThis.Document = ctor;
20
- (globalThis as typeof globalThis & { window: typeof globalThis }).window.Document = ctor;
21
- return;
22
- }
23
- }
24
- }
1
+ /**
2
+ * happy-dom exposes a `Document` constructor on the window that is *not* in the
3
+ * prototype chain of the actual `document` (which is an `HTMLDocument` built on
4
+ * happy-dom's internal `Document` class). `document instanceof Document` is
5
+ * therefore `false`, which is enough to hang libraries that walk up the tree
6
+ * until they reach the document — the `@oddbird/popover-polyfill` bundled into
7
+ * `@digdir/designsystemet-web` loops forever in `nearestInclusiveOpenPopover`,
8
+ * taking the whole worker with it.
9
+ *
10
+ * Point the global `Document` at the class the document really inherits from.
11
+ * Must run before anything that captures `Document`.
12
+ */
13
+ export function fixHappyDomDocumentClass(): void {
14
+ if (typeof document === "undefined" || document instanceof Document) return;
15
+
16
+ for (let proto = Object.getPrototypeOf(document); proto; proto = Object.getPrototypeOf(proto)) {
17
+ let ctor = proto.constructor;
18
+ if (ctor?.name === "Document") {
19
+ globalThis.Document = ctor;
20
+ (globalThis as typeof globalThis & { window: typeof globalThis }).window.Document = ctor;
21
+ return;
22
+ }
23
+ }
24
+ }
package/src/test/setup.ts CHANGED
@@ -1,27 +1,27 @@
1
- import { fixHappyDomDocumentClass } from "./happy-dom-document-class.js";
2
-
3
- fixHappyDomDocumentClass();
4
-
5
- import "@testing-library/jest-dom/vitest";
6
-
7
- import type { TestingLibraryMatchers } from "@testing-library/jest-dom/matchers";
8
-
9
- import { cleanup } from "@testing-library/react";
10
- import { afterEach } from "vitest";
11
-
12
- afterEach(() => {
13
- cleanup();
14
- });
15
-
16
- /*
17
- * jest-dom 7.0.1 types its matchers onto the one-parameter `Assertion<T>` of Vitest 4, which
18
- * Vitest 5 replaced with `Assertion<R, T>`; custom matchers now go on `Matchers<R, T>`. Until
19
- * jest-dom ships Vitest 5 types, register them there. Remove this when its `vitest` entry
20
- * types `Matchers` itself.
21
- */
22
- declare module "vitest" {
23
- interface Matchers<R extends void | Promise<void> = void | Promise<void>, T = unknown> extends TestingLibraryMatchers<
24
- T,
25
- R
26
- > {}
27
- }
1
+ import { fixHappyDomDocumentClass } from "./happy-dom-document-class.js";
2
+
3
+ fixHappyDomDocumentClass();
4
+
5
+ import "@testing-library/jest-dom/vitest";
6
+
7
+ import type { TestingLibraryMatchers } from "@testing-library/jest-dom/matchers";
8
+
9
+ import { cleanup } from "@testing-library/react";
10
+ import { afterEach } from "vitest";
11
+
12
+ afterEach(() => {
13
+ cleanup();
14
+ });
15
+
16
+ /*
17
+ * jest-dom 7.0.1 types its matchers onto the one-parameter `Assertion<T>` of Vitest 4, which
18
+ * Vitest 5 replaced with `Assertion<R, T>`; custom matchers now go on `Matchers<R, T>`. Until
19
+ * jest-dom ships Vitest 5 types, register them there. Remove this when its `vitest` entry
20
+ * types `Matchers` itself.
21
+ */
22
+ declare module "vitest" {
23
+ interface Matchers<R extends void | Promise<void> = void | Promise<void>, T = unknown> extends TestingLibraryMatchers<
24
+ T,
25
+ R
26
+ > {}
27
+ }
@@ -0,0 +1,62 @@
1
+ import { type RefObject, useLayoutEffect, useState } from "react";
2
+
3
+ /** Components whose `data-size` picks a variant of their own (text or avatar size), not the size mode. */
4
+ const SIZE_VARIANTS = ".ds-heading, .ds-paragraph, .ds-spinner, .ds-avatar";
5
+
6
+ /**
7
+ * The Designsystemet size mode an element is in: the `data-size` of its closest ancestor (or
8
+ * itself) that sets one, or null when nothing does and the page default applies.
9
+ */
10
+ export function getSizeMode(element: Element | null | undefined) {
11
+ return element?.closest(`[data-size]:not(${SIZE_VARIANTS})`)?.getAttribute("data-size") ?? null;
12
+ }
13
+
14
+ /**
15
+ * Gives an element rendered outside its owner - a dialog or popover portaled to `<body>` - the
16
+ * size mode of the element it belongs to, which it would otherwise not inherit.
17
+ */
18
+ export function copySizeMode(target: HTMLElement | null | undefined, owner: Element | null | undefined) {
19
+ if (!target) return;
20
+
21
+ let size = getSizeMode(owner);
22
+ if (size) {
23
+ target.dataset.size = size;
24
+ } else {
25
+ delete target.dataset.size;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * The pixel value of `--ds-size-<step>` at an element's position. The token is a `calc()` that
31
+ * a custom property hands over unresolved, so it is measured on a hidden probe instead. 0 when
32
+ * the Designsystemet theme is not loaded.
33
+ */
34
+ export function measureDsSize(element: Element, step: number) {
35
+ let probe = element.ownerDocument.createElement("div");
36
+ probe.style.cssText = `position: absolute; visibility: hidden; block-size: var(--ds-size-${step});`;
37
+ element.append(probe);
38
+ let height = probe.getBoundingClientRect().height;
39
+ probe.remove();
40
+
41
+ return height;
42
+ }
43
+
44
+ /**
45
+ * A height for something sized in script (a virtualized row, a grid row) that follows the size
46
+ * mode the element sits in: `--ds-size-<step>`, or `fallback` until it is measured and when the
47
+ * theme is not loaded. Measured once, after mount and before paint: the size mode of a page does
48
+ * not change while it is open.
49
+ */
50
+ export function useDsSize(ref: RefObject<Element | null>, step: number, fallback: number) {
51
+ let [size, setSize] = useState(fallback);
52
+
53
+ useLayoutEffect(() => {
54
+ let element = ref.current;
55
+ if (!element) return;
56
+
57
+ let measured = Math.round(measureDsSize(element, step));
58
+ if (measured) setSize(measured);
59
+ }, [ref, step]);
60
+
61
+ return size;
62
+ }