@simplysm/solid 13.0.84 → 13.0.86

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 (180) hide show
  1. package/README.md +143 -28
  2. package/dist/components/data/list/ListItem.d.ts.map +1 -1
  3. package/dist/components/data/list/ListItem.js +11 -4
  4. package/dist/components/data/list/ListItem.js.map +2 -2
  5. package/dist/components/data/list/ListItem.styles.d.ts +2 -0
  6. package/dist/components/data/list/ListItem.styles.d.ts.map +1 -1
  7. package/dist/components/data/list/ListItem.styles.js +11 -1
  8. package/dist/components/data/list/ListItem.styles.js.map +1 -1
  9. package/dist/components/data/sheet/DataSheet.d.ts.map +1 -1
  10. package/dist/components/data/sheet/DataSheet.js +6 -9
  11. package/dist/components/data/sheet/DataSheet.js.map +2 -2
  12. package/dist/components/data/sheet/hooks/createDataSheetExpansion.d.ts.map +1 -1
  13. package/dist/components/data/sheet/hooks/createDataSheetExpansion.js +15 -17
  14. package/dist/components/data/sheet/hooks/createDataSheetExpansion.js.map +1 -1
  15. package/dist/components/data/sheet/hooks/createDataSheetReorder.d.ts.map +1 -1
  16. package/dist/components/data/sheet/hooks/createDataSheetReorder.js +12 -12
  17. package/dist/components/data/sheet/hooks/createDataSheetReorder.js.map +1 -1
  18. package/dist/components/data/sheet/hooks/createDataSheetSelection.d.ts.map +1 -1
  19. package/dist/components/data/sheet/hooks/createDataSheetSelection.js +9 -3
  20. package/dist/components/data/sheet/hooks/createDataSheetSelection.js.map +1 -1
  21. package/dist/components/disclosure/Dialog.d.ts.map +1 -1
  22. package/dist/components/disclosure/Dialog.js +3 -21
  23. package/dist/components/disclosure/Dialog.js.map +2 -2
  24. package/dist/components/disclosure/Dropdown.d.ts.map +1 -1
  25. package/dist/components/disclosure/Dropdown.js +1 -11
  26. package/dist/components/disclosure/Dropdown.js.map +2 -2
  27. package/dist/components/disclosure/Tabs.d.ts.map +1 -1
  28. package/dist/components/disclosure/Tabs.js +1 -3
  29. package/dist/components/disclosure/Tabs.js.map +2 -2
  30. package/dist/components/features/crud-detail/CrudDetail.js +103 -102
  31. package/dist/components/features/crud-detail/CrudDetail.js.map +2 -2
  32. package/dist/components/features/crud-sheet/CrudSheet.d.ts.map +1 -1
  33. package/dist/components/features/crud-sheet/CrudSheet.js +10 -5
  34. package/dist/components/features/crud-sheet/CrudSheet.js.map +2 -2
  35. package/dist/components/features/data-select-button/DataSelectButton.d.ts.map +1 -1
  36. package/dist/components/features/data-select-button/DataSelectButton.js +30 -26
  37. package/dist/components/features/data-select-button/DataSelectButton.js.map +2 -2
  38. package/dist/components/features/permission-table/PermissionTable.js +5 -1
  39. package/dist/components/features/permission-table/PermissionTable.js.map +2 -2
  40. package/dist/components/feedback/busy/BusyContainer.d.ts.map +1 -1
  41. package/dist/components/feedback/busy/BusyContainer.js +1 -6
  42. package/dist/components/feedback/busy/BusyContainer.js.map +2 -2
  43. package/dist/components/form-control/DropdownTrigger.styles.js +1 -1
  44. package/dist/components/form-control/checkbox/SelectableBase.d.ts.map +1 -1
  45. package/dist/components/form-control/checkbox/SelectableBase.js +2 -4
  46. package/dist/components/form-control/checkbox/SelectableBase.js.map +2 -2
  47. package/dist/components/form-control/combobox/Combobox.d.ts +19 -5
  48. package/dist/components/form-control/combobox/Combobox.d.ts.map +1 -1
  49. package/dist/components/form-control/combobox/Combobox.js +2 -4
  50. package/dist/components/form-control/combobox/Combobox.js.map +1 -1
  51. package/dist/components/form-control/date-range-picker/DateRangePicker.d.ts +2 -2
  52. package/dist/components/form-control/date-range-picker/DateRangePicker.d.ts.map +1 -1
  53. package/dist/components/form-control/date-range-picker/DateRangePicker.js +11 -3
  54. package/dist/components/form-control/date-range-picker/DateRangePicker.js.map +2 -2
  55. package/dist/components/form-control/editor/RichTextEditor.d.ts +2 -2
  56. package/dist/components/form-control/editor/RichTextEditor.d.ts.map +1 -1
  57. package/dist/components/form-control/editor/RichTextEditor.js +2 -4
  58. package/dist/components/form-control/editor/RichTextEditor.js.map +2 -2
  59. package/dist/components/form-control/field/DatePicker.d.ts +2 -2
  60. package/dist/components/form-control/field/DatePicker.d.ts.map +1 -1
  61. package/dist/components/form-control/field/DatePicker.js.map +1 -1
  62. package/dist/components/form-control/field/DateTimePicker.d.ts +2 -2
  63. package/dist/components/form-control/field/DateTimePicker.d.ts.map +1 -1
  64. package/dist/components/form-control/field/DateTimePicker.js.map +1 -1
  65. package/dist/components/form-control/field/Field.styles.d.ts +6 -7
  66. package/dist/components/form-control/field/Field.styles.d.ts.map +1 -1
  67. package/dist/components/form-control/field/Field.styles.js.map +1 -1
  68. package/dist/components/form-control/field/NumberInput.d.ts +2 -2
  69. package/dist/components/form-control/field/NumberInput.d.ts.map +1 -1
  70. package/dist/components/form-control/field/NumberInput.js +7 -7
  71. package/dist/components/form-control/field/NumberInput.js.map +2 -2
  72. package/dist/components/form-control/field/TextInput.d.ts +2 -2
  73. package/dist/components/form-control/field/TextInput.d.ts.map +1 -1
  74. package/dist/components/form-control/field/TextInput.js.map +1 -1
  75. package/dist/components/form-control/field/Textarea.d.ts +2 -2
  76. package/dist/components/form-control/field/Textarea.d.ts.map +1 -1
  77. package/dist/components/form-control/field/Textarea.js +1 -3
  78. package/dist/components/form-control/field/Textarea.js.map +2 -2
  79. package/dist/components/form-control/field/TimePicker.d.ts +2 -2
  80. package/dist/components/form-control/field/TimePicker.d.ts.map +1 -1
  81. package/dist/components/form-control/field/TimePicker.js.map +1 -1
  82. package/dist/components/form-control/numpad/Numpad.d.ts.map +1 -1
  83. package/dist/components/form-control/numpad/Numpad.js +4 -17
  84. package/dist/components/form-control/numpad/Numpad.js.map +2 -2
  85. package/dist/components/form-control/select/Select.d.ts +2 -0
  86. package/dist/components/form-control/select/Select.d.ts.map +1 -1
  87. package/dist/components/form-control/select/Select.js +29 -15
  88. package/dist/components/form-control/select/Select.js.map +2 -2
  89. package/dist/components/form-control/state-preset/StatePreset.d.ts +1 -3
  90. package/dist/components/form-control/state-preset/StatePreset.d.ts.map +1 -1
  91. package/dist/components/form-control/state-preset/StatePreset.js +69 -95
  92. package/dist/components/form-control/state-preset/StatePreset.js.map +2 -2
  93. package/dist/components/layout/FormGroup.js +1 -1
  94. package/dist/components/layout/FormGroup.js.map +1 -1
  95. package/dist/components/layout/FormTable.js +3 -3
  96. package/dist/components/layout/FormTable.js.map +1 -1
  97. package/dist/components/layout/sidebar/Sidebar.d.ts.map +1 -1
  98. package/dist/components/layout/sidebar/Sidebar.js +3 -6
  99. package/dist/components/layout/sidebar/Sidebar.js.map +2 -2
  100. package/dist/components/layout/topbar/Topbar.js +1 -3
  101. package/dist/components/layout/topbar/Topbar.js.map +2 -2
  102. package/dist/hooks/createControllableStore.d.ts.map +1 -1
  103. package/dist/hooks/createControllableStore.js +8 -5
  104. package/dist/hooks/createControllableStore.js.map +1 -1
  105. package/dist/hooks/useLocalStorage.d.ts.map +1 -1
  106. package/dist/hooks/useLocalStorage.js +3 -2
  107. package/dist/hooks/useLocalStorage.js.map +1 -1
  108. package/dist/hooks/useSyncConfig.d.ts.map +1 -1
  109. package/dist/hooks/useSyncConfig.js +5 -4
  110. package/dist/hooks/useSyncConfig.js.map +1 -1
  111. package/dist/providers/i18n/locales/en.d.ts +2 -3
  112. package/dist/providers/i18n/locales/en.d.ts.map +1 -1
  113. package/dist/providers/i18n/locales/en.js +3 -4
  114. package/dist/providers/i18n/locales/en.js.map +1 -1
  115. package/dist/providers/i18n/locales/ko.d.ts +2 -3
  116. package/dist/providers/i18n/locales/ko.d.ts.map +1 -1
  117. package/dist/providers/i18n/locales/ko.js +3 -4
  118. package/dist/providers/i18n/locales/ko.js.map +1 -1
  119. package/dist/providers/shared-data/SharedDataProvider.d.ts.map +1 -1
  120. package/dist/providers/shared-data/SharedDataProvider.js +0 -1
  121. package/dist/providers/shared-data/SharedDataProvider.js.map +1 -1
  122. package/docs/display-feedback.md +279 -0
  123. package/docs/features.md +357 -213
  124. package/docs/form-controls.md +261 -403
  125. package/docs/layout-data.md +386 -0
  126. package/docs/providers-hooks.md +411 -0
  127. package/package.json +5 -5
  128. package/src/components/data/list/ListItem.styles.ts +14 -2
  129. package/src/components/data/list/ListItem.tsx +13 -4
  130. package/src/components/data/sheet/DataSheet.tsx +6 -10
  131. package/src/components/data/sheet/hooks/createDataSheetExpansion.ts +17 -18
  132. package/src/components/data/sheet/hooks/createDataSheetReorder.ts +12 -13
  133. package/src/components/data/sheet/hooks/createDataSheetSelection.ts +9 -3
  134. package/src/components/disclosure/Dialog.tsx +45 -59
  135. package/src/components/disclosure/Dropdown.tsx +4 -14
  136. package/src/components/disclosure/Tabs.tsx +12 -17
  137. package/src/components/features/crud-detail/CrudDetail.tsx +4 -4
  138. package/src/components/features/crud-sheet/CrudSheet.tsx +12 -5
  139. package/src/components/features/data-select-button/DataSelectButton.tsx +39 -32
  140. package/src/components/features/permission-table/PermissionTable.tsx +1 -1
  141. package/src/components/feedback/busy/BusyContainer.tsx +12 -18
  142. package/src/components/form-control/DropdownTrigger.styles.ts +1 -1
  143. package/src/components/form-control/checkbox/SelectableBase.tsx +10 -16
  144. package/src/components/form-control/combobox/Combobox.tsx +42 -16
  145. package/src/components/form-control/date-range-picker/DateRangePicker.tsx +7 -8
  146. package/src/components/form-control/editor/RichTextEditor.tsx +14 -16
  147. package/src/components/form-control/field/DatePicker.tsx +3 -2
  148. package/src/components/form-control/field/DateTimePicker.tsx +3 -2
  149. package/src/components/form-control/field/Field.styles.ts +6 -8
  150. package/src/components/form-control/field/NumberInput.tsx +9 -10
  151. package/src/components/form-control/field/TextInput.tsx +3 -2
  152. package/src/components/form-control/field/Textarea.tsx +14 -12
  153. package/src/components/form-control/field/TimePicker.tsx +3 -2
  154. package/src/components/form-control/numpad/Numpad.tsx +16 -18
  155. package/src/components/form-control/select/Select.tsx +41 -13
  156. package/src/components/form-control/state-preset/StatePreset.tsx +39 -71
  157. package/src/components/layout/FormGroup.tsx +1 -1
  158. package/src/components/layout/FormTable.tsx +3 -3
  159. package/src/components/layout/sidebar/Sidebar.tsx +2 -3
  160. package/src/components/layout/topbar/Topbar.tsx +2 -2
  161. package/src/hooks/createControllableStore.ts +8 -4
  162. package/src/hooks/useLocalStorage.ts +3 -2
  163. package/src/hooks/useSyncConfig.ts +5 -4
  164. package/src/providers/i18n/locales/en.ts +2 -3
  165. package/src/providers/i18n/locales/ko.ts +2 -3
  166. package/src/providers/shared-data/SharedDataProvider.tsx +0 -1
  167. package/tests/components/features/crud-detail/CrudDetail.spec.tsx +49 -0
  168. package/tests/components/features/data-select-button/DataSelectButton.spec.tsx +62 -7
  169. package/tests/components/form-control/combobox/Combobox.spec.tsx +3 -3
  170. package/tests/components/form-control/date-range-picker/DateRangePicker.spec.tsx +56 -0
  171. package/tests/components/form-control/select/SelectItem.spec.tsx +5 -0
  172. package/tests/providers/shared-data/SharedDataProvider.spec.tsx +0 -104
  173. package/docs/data.md +0 -204
  174. package/docs/disclosure.md +0 -146
  175. package/docs/display.md +0 -125
  176. package/docs/feedback.md +0 -156
  177. package/docs/helpers.md +0 -173
  178. package/docs/hooks.md +0 -146
  179. package/docs/layout.md +0 -94
  180. package/docs/providers.md +0 -180
@@ -395,9 +395,8 @@ describe("DataSelectButton", () => {
395
395
  />
396
396
  ));
397
397
 
398
- const trigger = container.querySelector("[role='combobox']") as HTMLElement;
399
- expect(trigger.getAttribute("aria-disabled")).toBe("true");
400
- expect(trigger.tabIndex).toBe(-1);
398
+ const trigger = container.querySelector("button[data-trigger]") as HTMLButtonElement;
399
+ expect(trigger.disabled).toBe(true);
401
400
  });
402
401
 
403
402
  it("sets required aria attribute", () => {
@@ -412,11 +411,67 @@ describe("DataSelectButton", () => {
412
411
  />
413
412
  ));
414
413
 
415
- const trigger = container.querySelector("[role='combobox']") as HTMLElement;
414
+ const trigger = container.querySelector("button[data-trigger]") as HTMLElement;
416
415
  expect(trigger.getAttribute("aria-required")).toBe("true");
417
416
  });
418
417
 
419
- it("opens dialog on Enter key press", async () => {
418
+ it("trigger is a button element with aria-haspopup='dialog'", () => {
419
+ const load = createTestLoad();
420
+ const { container } = renderWithDialog(() => (
421
+ <DataSelectButton
422
+ load={load}
423
+ dialog={TestDialogComponent}
424
+ dialogProps={{ confirmKeys: [] }}
425
+ renderItem={(item: TestItem) => <span>{item.name}</span>}
426
+ />
427
+ ));
428
+
429
+ const trigger = container.querySelector("button[data-trigger]") as HTMLButtonElement;
430
+ expect(trigger).not.toBeNull();
431
+ expect(trigger.tagName).toBe("BUTTON");
432
+ expect(trigger.getAttribute("aria-haspopup")).toBe("dialog");
433
+ expect(trigger.getAttribute("type")).toBe("button");
434
+ });
435
+
436
+ it("aria-expanded changes dynamically with dialog open state", async () => {
437
+ const load = createTestLoad();
438
+
439
+ const { container } = renderWithDialog(() => (
440
+ <DataSelectButton
441
+ load={load}
442
+ dialog={TestDialogComponent}
443
+ dialogProps={{ confirmKeys: [1] }}
444
+ renderItem={(item: TestItem) => <span>{item.name}</span>}
445
+ />
446
+ ));
447
+
448
+ const trigger = container.querySelector("button[data-trigger]") as HTMLButtonElement;
449
+
450
+ // Initially false
451
+ expect(trigger.getAttribute("aria-expanded")).toBe("false");
452
+
453
+ // Open dialog via search button
454
+ const searchBtn = container.querySelector("[data-search-button]") as HTMLButtonElement;
455
+ searchBtn.click();
456
+
457
+ // While dialog is open, aria-expanded should be true
458
+ await vi.waitFor(() => {
459
+ expect(trigger.getAttribute("aria-expanded")).toBe("true");
460
+ });
461
+
462
+ // Confirm dialog to close it
463
+ const confirmBtn = document.querySelector(
464
+ "[data-testid='dialog-confirm']",
465
+ ) as HTMLButtonElement;
466
+ confirmBtn.click();
467
+
468
+ // After dialog closes, aria-expanded should be false again
469
+ await vi.waitFor(() => {
470
+ expect(trigger.getAttribute("aria-expanded")).toBe("false");
471
+ });
472
+ });
473
+
474
+ it("opens dialog on trigger click", async () => {
420
475
  const load = createTestLoad();
421
476
  const onValueChange = vi.fn();
422
477
 
@@ -430,8 +485,8 @@ describe("DataSelectButton", () => {
430
485
  />
431
486
  ));
432
487
 
433
- const trigger = container.querySelector("[role='combobox']") as HTMLElement;
434
- trigger.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
488
+ const trigger = container.querySelector("button[data-trigger]") as HTMLElement;
489
+ trigger.click();
435
490
 
436
491
  await vi.waitFor(() => {
437
492
  const confirmBtn = document.querySelector(
@@ -143,7 +143,7 @@ describe("Combobox component", () => {
143
143
  onValueChange={handleChange}
144
144
  allowsCustomValue
145
145
  parseCustomValue={(text) => text}
146
- renderValue={(v) => <>{v}</>}
146
+ renderValue={(v: string) => <>{v}</>}
147
147
  />
148
148
  </I18nProvider></ConfigProvider>
149
149
  ));
@@ -178,7 +178,7 @@ describe("Combobox component", () => {
178
178
  expect(handleChange).toHaveBeenCalledWith({ name: "테스트", custom: true });
179
179
  });
180
180
 
181
- it("sets undefined when allowsCustomValue is true without parseCustomValue", async () => {
181
+ it("uses query text as value when allowsCustomValue is true without parseCustomValue", async () => {
182
182
  const onValueChange = vi.fn();
183
183
  const { container, getByRole } = render(() => (
184
184
  <ConfigProvider clientName="test"><I18nProvider>
@@ -196,7 +196,7 @@ describe("Combobox component", () => {
196
196
  fireEvent.keyDown(getByRole("combobox"), { key: "Enter" });
197
197
 
198
198
  await waitFor(() => {
199
- expect(onValueChange).toHaveBeenCalledWith(undefined);
199
+ expect(onValueChange).toHaveBeenCalledWith("custom text");
200
200
  });
201
201
  });
202
202
  });
@@ -115,6 +115,62 @@ describe("DateRangePicker component", () => {
115
115
  });
116
116
  });
117
117
 
118
+ describe("required prop propagation", () => {
119
+ it("propagates required to child DatePickers in range mode", () => {
120
+ const { container } = render(() => (
121
+ <ConfigProvider clientName="test"><I18nProvider>
122
+ <DateRangePicker periodType="range" required />
123
+ </I18nProvider></ConfigProvider>
124
+ ));
125
+ const wrapper = container.querySelector("[data-date-range-picker]");
126
+ const dateFields = wrapper?.querySelectorAll("[data-date-field]");
127
+
128
+ // range mode has 2 DatePickers (from + to)
129
+ expect(dateFields?.length).toBe(2);
130
+
131
+ // Each DatePicker's hidden input should have the required validation message
132
+ dateFields?.forEach((field) => {
133
+ const hiddenInput = field.querySelector("input[aria-hidden='true']") as HTMLInputElement;
134
+ expect(hiddenInput).toBeTruthy();
135
+ expect(hiddenInput.validationMessage).toBe("This is a required field");
136
+ });
137
+ });
138
+
139
+ it("propagates required to child DatePicker in day mode", () => {
140
+ const { container } = render(() => (
141
+ <ConfigProvider clientName="test"><I18nProvider>
142
+ <DateRangePicker periodType="day" required />
143
+ </I18nProvider></ConfigProvider>
144
+ ));
145
+ const wrapper = container.querySelector("[data-date-range-picker]");
146
+ const dateFields = wrapper?.querySelectorAll("[data-date-field]");
147
+
148
+ // day mode has 1 DatePicker
149
+ expect(dateFields?.length).toBe(1);
150
+
151
+ const hiddenInput = dateFields?.[0].querySelector("input[aria-hidden='true']") as HTMLInputElement;
152
+ expect(hiddenInput).toBeTruthy();
153
+ expect(hiddenInput.validationMessage).toBe("This is a required field");
154
+ });
155
+
156
+ it("propagates required to child DatePicker in month mode", () => {
157
+ const { container } = render(() => (
158
+ <ConfigProvider clientName="test"><I18nProvider>
159
+ <DateRangePicker periodType="month" required />
160
+ </I18nProvider></ConfigProvider>
161
+ ));
162
+ const wrapper = container.querySelector("[data-date-range-picker]");
163
+ const dateFields = wrapper?.querySelectorAll("[data-date-field]");
164
+
165
+ // month mode has 1 DatePicker
166
+ expect(dateFields?.length).toBe(1);
167
+
168
+ const hiddenInput = dateFields?.[0].querySelector("input[aria-hidden='true']") as HTMLInputElement;
169
+ expect(hiddenInput).toBeTruthy();
170
+ expect(hiddenInput.validationMessage).toBe("This is a required field");
171
+ });
172
+ });
173
+
118
174
  describe("from change - 'range' mode", () => {
119
175
  it("calls onToChange(from) when from > to", () => {
120
176
  const onFromChange = vi.fn();
@@ -23,6 +23,7 @@ describe("SelectItem component", () => {
23
23
  toggleValue,
24
24
  closeDropdown: vi.fn(),
25
25
  setItemTemplate: vi.fn(),
26
+ size: () => "md" as const,
26
27
  };
27
28
 
28
29
  const { getByText } = render(() => (
@@ -43,6 +44,7 @@ describe("SelectItem component", () => {
43
44
  toggleValue: vi.fn(),
44
45
  closeDropdown,
45
46
  setItemTemplate: vi.fn(),
47
+ size: () => "md" as const,
46
48
  };
47
49
 
48
50
  const { getByText } = render(() => (
@@ -63,6 +65,7 @@ describe("SelectItem component", () => {
63
65
  toggleValue: vi.fn(),
64
66
  closeDropdown,
65
67
  setItemTemplate: vi.fn(),
68
+ size: () => "md" as const,
66
69
  };
67
70
 
68
71
  const { getByText } = render(() => (
@@ -84,6 +87,7 @@ describe("SelectItem component", () => {
84
87
  toggleValue: vi.fn(),
85
88
  closeDropdown: vi.fn(),
86
89
  setItemTemplate: vi.fn(),
90
+ size: () => "md" as const,
87
91
  };
88
92
 
89
93
  render(() => (
@@ -106,6 +110,7 @@ describe("SelectItem component", () => {
106
110
  toggleValue,
107
111
  closeDropdown: vi.fn(),
108
112
  setItemTemplate: vi.fn(),
113
+ size: () => "md" as const,
109
114
  };
110
115
 
111
116
  const { getByText } = render(() => (
@@ -418,108 +418,4 @@ describe("SharedDataProvider", () => {
418
418
  result.unmount();
419
419
  });
420
420
 
421
- it("fetches eagerly after configure()", async () => {
422
- const { serviceClientValue, mockClient } = createMockServiceClient();
423
- const mockUsers: TestUser[] = [{ id: 1, name: "Alice" }];
424
-
425
- const fetchFn = vi.fn(() => Promise.resolve(mockUsers));
426
-
427
- const definitions: { user: SharedDataDefinition<TestUser> } = {
428
- user: {
429
- fetch: fetchFn,
430
- getKey: (item) => item.id,
431
- orderBy: [[(item) => item.name, "asc"]],
432
- },
433
- };
434
-
435
- function ConfigureOnly() {
436
- const shared = useTestSharedData();
437
- shared.configure(() => definitions);
438
- return <div data-testid="configured">configured</div>;
439
- }
440
-
441
- const result = render(() => (
442
- <NotificationContext.Provider value={createMockNotification()}>
443
- <ServiceClientContext.Provider value={serviceClientValue}>
444
- <SharedDataProvider>
445
- <ConfigureOnly />
446
- </SharedDataProvider>
447
- </ServiceClientContext.Provider>
448
- </NotificationContext.Provider>
449
- ));
450
-
451
- await vi.waitFor(() => {
452
- expect(result.getByTestId("configured").textContent).toBe("configured");
453
- });
454
-
455
- // Now configure() triggers eager init
456
- await vi.waitFor(() => {
457
- expect(fetchFn).toHaveBeenCalledTimes(1);
458
- expect(mockClient.addListener).toHaveBeenCalledTimes(1);
459
- });
460
-
461
- result.unmount();
462
- });
463
-
464
- it("wait() resolves after data is loaded even without items() access", async () => {
465
- const { serviceClientValue } = createMockServiceClient();
466
-
467
- let resolveUsers!: (value: TestUser[]) => void;
468
- const fetchPromise = new Promise<TestUser[]>((resolve) => {
469
- resolveUsers = resolve;
470
- });
471
-
472
- const fetchFn = vi.fn(() => fetchPromise);
473
-
474
- const definitions: { user: SharedDataDefinition<TestUser> } = {
475
- user: {
476
- fetch: fetchFn,
477
- getKey: (item) => item.id,
478
- orderBy: [[(item) => item.name, "asc"]],
479
- },
480
- };
481
-
482
- let waitResolved = false;
483
-
484
- // Component that calls configure() + wait() but never accesses items()
485
- function ConfigureAndWait() {
486
- const shared = useTestSharedData();
487
- shared.configure(() => definitions);
488
-
489
- // Call wait() immediately — should NOT resolve until fetch completes
490
- void shared.wait().then(() => {
491
- waitResolved = true;
492
- });
493
-
494
- return <div data-testid="ready">{String(waitResolved)}</div>;
495
- }
496
-
497
- const result = render(() => (
498
- <NotificationContext.Provider value={createMockNotification()}>
499
- <ServiceClientContext.Provider value={serviceClientValue}>
500
- <SharedDataProvider>
501
- <ConfigureAndWait />
502
- </SharedDataProvider>
503
- </ServiceClientContext.Provider>
504
- </NotificationContext.Provider>
505
- ));
506
-
507
- // fetch should have been called (eager init)
508
- await vi.waitFor(() => {
509
- expect(fetchFn).toHaveBeenCalledTimes(1);
510
- });
511
-
512
- // wait() should NOT have resolved yet (fetch still pending)
513
- expect(waitResolved).toBe(false);
514
-
515
- // Resolve the fetch
516
- resolveUsers([{ id: 1, name: "Alice" }]);
517
-
518
- // wait() should now resolve
519
- await vi.waitFor(() => {
520
- expect(waitResolved).toBe(true);
521
- });
522
-
523
- result.unmount();
524
- });
525
421
  });
package/docs/data.md DELETED
@@ -1,204 +0,0 @@
1
- # Data
2
-
3
- ## Table
4
-
5
- ```typescript
6
- interface TableProps extends JSX.HTMLAttributes<HTMLTableElement> {
7
- inset?: boolean;
8
- }
9
- ```
10
-
11
- Basic HTML table with consistent styling.
12
-
13
- **Sub-components:** `Table.Row`, `Table.HeaderCell`, `Table.Cell`
14
-
15
- ---
16
-
17
- ## List
18
-
19
- ```typescript
20
- interface ListProps extends JSX.HTMLAttributes<HTMLDivElement> {
21
- inset?: boolean;
22
- }
23
- ```
24
-
25
- Vertical list with keyboard navigation (ArrowUp/Down, Home/End) and tree-view support (ArrowRight/Left to expand/collapse).
26
-
27
- **Sub-component:** `List.Item`
28
-
29
- ---
30
-
31
- ## Pagination
32
-
33
- ```typescript
34
- interface PaginationProps extends JSX.HTMLAttributes<HTMLElement> {
35
- page: number;
36
- onPageChange?: (page: number) => void;
37
- totalPageCount: number;
38
- displayPageCount?: number;
39
- size?: "xs" | "sm" | "md" | "lg" | "xl";
40
- }
41
- ```
42
-
43
- Page navigation control. `page` is 1-based. `displayPageCount` controls how many page numbers are visible at once.
44
-
45
- ---
46
-
47
- ## DataSheet
48
-
49
- ```typescript
50
- interface DataSheetProps<TItem> {
51
- items?: TItem[];
52
- storageKey?: string;
53
- hideConfigBar?: boolean;
54
- inset?: boolean;
55
- contentStyle?: JSX.CSSProperties | string;
56
-
57
- // Sorting
58
- sorts?: SortingDef[];
59
- onSortsChange?: (sorts: SortingDef[]) => void;
60
- autoSort?: boolean;
61
-
62
- // Pagination
63
- page?: number;
64
- onPageChange?: (page: number) => void;
65
- totalPageCount?: number;
66
- pageSize?: number;
67
- displayPageCount?: number;
68
-
69
- // Selection
70
- selectionMode?: "single" | "multiple";
71
- selection?: TItem[];
72
- onSelectionChange?: (items: TItem[]) => void;
73
- autoSelect?: boolean;
74
- isItemSelectable?: (item: TItem) => boolean | string;
75
-
76
- // Tree expansion
77
- expandedItems?: TItem[];
78
- onExpandedItemsChange?: (items: TItem[]) => void;
79
- itemChildren?: (item: TItem, index: number) => TItem[] | undefined;
80
-
81
- // Cell styling
82
- cellClass?: (item: TItem, colKey: string) => string | undefined;
83
- cellStyle?: (item: TItem, colKey: string) => string | undefined;
84
-
85
- // Reordering
86
- onItemsReorder?: (event: DataSheetReorderEvent<TItem>) => void;
87
-
88
- class?: string;
89
- children: JSX.Element;
90
- }
91
- ```
92
-
93
- Advanced data grid with sorting, pagination, row selection, tree expansion, column reordering, and column configuration persistence.
94
-
95
- - `storageKey` — persists column configuration (width, visibility, order) to sync storage
96
- - `autoSort` — sorts items client-side without `onSortsChange`
97
- - `autoSelect` — manages selection state internally
98
- - `itemChildren` — enables tree-structured rows
99
-
100
- **Sub-component:** `DataSheet.Column`
101
-
102
- ```typescript
103
- interface DataSheetColumnProps<TItem> {
104
- key: string;
105
- header?: string | string[];
106
- headerContent?: () => JSX.Element;
107
- headerStyle?: string;
108
- summary?: () => JSX.Element;
109
- tooltip?: string;
110
- fixed?: boolean;
111
- hidden?: boolean;
112
- collapse?: boolean;
113
- width?: string;
114
- class?: string;
115
- sortable?: boolean;
116
- resizable?: boolean;
117
- children: (ctx: DataSheetCellContext<TItem>) => JSX.Element;
118
- }
119
-
120
- interface DataSheetCellContext<TItem> {
121
- item: TItem;
122
- index: number;
123
- row: number;
124
- depth: number;
125
- }
126
-
127
- interface SortingDef {
128
- key: string;
129
- desc: boolean;
130
- }
131
-
132
- interface DataSheetReorderEvent<TItem> {
133
- item: TItem;
134
- targetItem: TItem;
135
- position: "before" | "after" | "inside";
136
- }
137
- ```
138
-
139
- ---
140
-
141
- ## Calendar
142
-
143
- ```typescript
144
- interface CalendarProps<TValue> extends Omit<JSX.HTMLAttributes<HTMLTableElement>, "children"> {
145
- items: TValue[];
146
- getItemDate: (item: TValue, index: number) => DateOnly;
147
- renderItem: (item: TValue, index: number) => JSX.Element;
148
- yearMonth?: DateOnly;
149
- onYearMonthChange?: (value: DateOnly) => void;
150
- weekStartDay?: number;
151
- minDaysInFirstWeek?: number;
152
- }
153
- ```
154
-
155
- Monthly calendar view that renders items on their corresponding dates. `getItemDate` maps items to dates. `renderItem` renders each item cell.
156
-
157
- ---
158
-
159
- ## Kanban
160
-
161
- ```typescript
162
- interface KanbanProps<TCardValue, TLaneValue> extends Omit<JSX.HTMLAttributes<HTMLDivElement>, "children" | "onDrop"> {
163
- onDrop?: (info: KanbanDropInfo<TLaneValue, TCardValue>) => void;
164
- selectedValues?: TCardValue[];
165
- onSelectedValuesChange?: (values: TCardValue[]) => void;
166
- children?: JSX.Element;
167
- }
168
- ```
169
-
170
- Kanban board with drag-and-drop card management.
171
-
172
- **Sub-components:**
173
- - `Kanban.Lane` — `{ value?: TLaneValue; busy?: boolean; collapsible?: boolean; collapsed?: boolean; onCollapsedChange?: (collapsed: boolean) => void }`
174
- - `Kanban.Card` — `{ value?: TCardValue; draggable?: boolean; selectable?: boolean; contentClass?: string }`
175
- - `Kanban.LaneTitle` — lane title slot
176
- - `Kanban.LaneTools` — lane toolbar slot
177
-
178
- ---
179
-
180
- ## Usage Examples
181
-
182
- ```typescript
183
- import { DataSheet, Pagination } from "@simplysm/solid";
184
-
185
- <DataSheet
186
- items={data()}
187
- sorts={sorts()}
188
- onSortsChange={setSorts}
189
- page={page()}
190
- onPageChange={setPage}
191
- totalPageCount={totalPages()}
192
- selectionMode="multiple"
193
- selection={selected()}
194
- onSelectionChange={setSelected}
195
- storageKey="my-table"
196
- >
197
- <DataSheet.Column key="name" header="Name" sortable>
198
- {(ctx) => ctx.item.name}
199
- </DataSheet.Column>
200
- <DataSheet.Column key="age" header="Age" sortable width="80px">
201
- {(ctx) => ctx.item.age}
202
- </DataSheet.Column>
203
- </DataSheet>
204
- ```
@@ -1,146 +0,0 @@
1
- # Disclosure
2
-
3
- ## Collapse
4
-
5
- ```typescript
6
- interface CollapseProps extends JSX.HTMLAttributes<HTMLDivElement> {
7
- open?: boolean;
8
- }
9
- ```
10
-
11
- Collapsible content panel with CSS transition. Content is hidden when `open` is `false`.
12
-
13
- ---
14
-
15
- ## Dropdown
16
-
17
- ```typescript
18
- interface DropdownProps {
19
- position?: { x: number; y: number };
20
- open?: boolean;
21
- onOpenChange?: (open: boolean) => void;
22
- maxHeight?: number;
23
- disabled?: boolean;
24
- keyboardNav?: boolean;
25
- class?: string;
26
- style?: JSX.CSSProperties;
27
- children: JSX.Element;
28
- }
29
- ```
30
-
31
- Dropdown popup anchored to a trigger element. When `position` is provided instead of a trigger, the popup appears at the given absolute coordinates (useful for context menus). `keyboardNav` enables ArrowUp/Down navigation. Default `maxHeight` is 300px.
32
-
33
- **Sub-components:**
34
- - `Dropdown.Trigger` -- the element that opens the dropdown on click
35
- - `Dropdown.Content` -- the popup content
36
-
37
- ---
38
-
39
- ## Dialog
40
-
41
- ```typescript
42
- interface DialogProps {
43
- open?: boolean;
44
- onOpenChange?: (open: boolean) => void;
45
- withCloseButton?: boolean;
46
- closeOnInteractOutside?: boolean;
47
- closeOnEscape?: boolean;
48
- resizable?: boolean;
49
- draggable?: boolean;
50
- mode?: "float" | "fill";
51
- width?: number;
52
- height?: number;
53
- minWidth?: number;
54
- minHeight?: number;
55
- position?: "bottom-right" | "top-right";
56
- headerStyle?: JSX.CSSProperties | string;
57
- beforeClose?: () => boolean;
58
- onCloseComplete?: () => void;
59
- class?: string;
60
- }
61
- ```
62
-
63
- Modal dialog with overlay. Supports both declarative (template) and programmatic usage.
64
-
65
- - `mode="float"` -- centered floating dialog (default)
66
- - `mode="fill"` -- full-screen dialog
67
- - `beforeClose` -- return `false` to prevent closing
68
- - `resizable` / `draggable` -- enable resize handles / drag-to-move (draggable defaults to `true`)
69
- - `withCloseButton` -- show close button (defaults to `true`)
70
- - `closeOnEscape` -- close on Escape key (defaults to `true`)
71
-
72
- **Sub-components:**
73
- - `Dialog.Header` -- dialog title bar
74
- - `Dialog.Action` -- dialog action buttons area
75
-
76
- ### Programmatic API
77
-
78
- ```typescript
79
- interface DialogContextValue {
80
- show<P>(
81
- component: Component<P>,
82
- props: Omit<P, "close">,
83
- options?: DialogShowOptions,
84
- ): Promise<ExtractCloseResult<P> | undefined>;
85
- }
86
-
87
- interface DialogProviderProps {
88
- closeOnEscape?: boolean;
89
- closeOnInteractOutside?: boolean;
90
- }
91
- ```
92
-
93
- Use `DialogProvider` and `useDialog()` for programmatic dialog management. The shown component receives a `close(result?)` prop to close the dialog and return a value. `DialogProvider` accepts default options for `closeOnEscape` and `closeOnInteractOutside`.
94
-
95
- ```typescript
96
- const dialog = useDialog();
97
- const result = await dialog.show(MyDialogContent, { data }, {
98
- width: 600,
99
- withCloseButton: true,
100
- });
101
- ```
102
-
103
- ---
104
-
105
- ## Tabs
106
-
107
- ```typescript
108
- interface TabsProps {
109
- value?: string;
110
- onValueChange?: (value: string) => void;
111
- size?: "xs" | "sm" | "md" | "lg" | "xl";
112
- class?: string;
113
- style?: JSX.CSSProperties;
114
- children?: JSX.Element;
115
- }
116
- ```
117
-
118
- Tab navigation. Content rendering is managed externally based on the selected `value`.
119
-
120
- **Sub-component:** `Tabs.Tab` -- `{ value: string; disabled?: boolean; class?: string; children?: JSX.Element }`
121
-
122
- ---
123
-
124
- ## Usage Examples
125
-
126
- ```typescript
127
- import { Dialog, Dropdown, Tabs, Collapse } from "@simplysm/solid";
128
-
129
- // Declarative dialog
130
- <Dialog open={isOpen()} onOpenChange={setIsOpen} width={500} withCloseButton>
131
- <Dialog.Header>Edit User</Dialog.Header>
132
- <form>...</form>
133
- <Dialog.Action>
134
- <Button onClick={() => setIsOpen(false)}>Cancel</Button>
135
- <Button theme="primary" onClick={save}>Save</Button>
136
- </Dialog.Action>
137
- </Dialog>
138
-
139
- // Tabs
140
- <Tabs value={tab()} onValueChange={setTab}>
141
- <Tabs.Tab value="general">General</Tabs.Tab>
142
- <Tabs.Tab value="settings">Settings</Tabs.Tab>
143
- </Tabs>
144
- <Show when={tab() === "general"}>General content</Show>
145
- <Show when={tab() === "settings"}>Settings content</Show>
146
- ```