@simplysm/solid 13.0.28 → 13.0.30

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 (379) hide show
  1. package/README.md +15 -9
  2. package/dist/components/data/Pagination.d.ts +4 -5
  3. package/dist/components/data/Pagination.d.ts.map +1 -1
  4. package/dist/components/data/Pagination.js +14 -14
  5. package/dist/components/data/Pagination.js.map +2 -2
  6. package/dist/components/data/Table.js +1 -1
  7. package/dist/components/data/calendar/Calendar.d.ts.map +1 -1
  8. package/dist/components/data/calendar/Calendar.js +1 -1
  9. package/dist/components/data/calendar/Calendar.js.map +1 -1
  10. package/dist/components/data/kanban/Kanban.d.ts +9 -9
  11. package/dist/components/data/kanban/Kanban.d.ts.map +1 -1
  12. package/dist/components/data/kanban/Kanban.js +6 -6
  13. package/dist/components/data/kanban/Kanban.js.map +2 -2
  14. package/dist/components/data/list/List.d.ts.map +1 -1
  15. package/dist/components/data/list/List.js.map +1 -1
  16. package/dist/components/data/list/ListItem.d.ts.map +1 -1
  17. package/dist/components/data/list/ListItem.js.map +1 -1
  18. package/dist/components/data/permission-table/PermissionTable.d.ts.map +1 -1
  19. package/dist/components/data/permission-table/PermissionTable.js.map +1 -1
  20. package/dist/components/data/sheet/DataSheet.d.ts.map +1 -1
  21. package/dist/components/data/sheet/DataSheet.js +102 -107
  22. package/dist/components/data/sheet/DataSheet.js.map +2 -2
  23. package/dist/components/data/sheet/DataSheet.styles.d.ts.map +1 -1
  24. package/dist/components/data/sheet/DataSheet.styles.js +24 -6
  25. package/dist/components/data/sheet/DataSheet.styles.js.map +1 -1
  26. package/dist/components/data/sheet/DataSheetColumn.d.ts.map +1 -1
  27. package/dist/components/data/sheet/DataSheetColumn.js.map +1 -1
  28. package/dist/components/data/sheet/DataSheetConfigDialog.d.ts.map +1 -1
  29. package/dist/components/data/sheet/DataSheetConfigDialog.js.map +1 -1
  30. package/dist/components/data/sheet/sheetUtils.d.ts.map +1 -1
  31. package/dist/components/data/sheet/sheetUtils.js.map +1 -1
  32. package/dist/components/data/sheet/types.d.ts +2 -2
  33. package/dist/components/data/sheet/types.d.ts.map +1 -1
  34. package/dist/components/disclosure/Collapse.d.ts.map +1 -1
  35. package/dist/components/disclosure/Collapse.js +0 -3
  36. package/dist/components/disclosure/Collapse.js.map +1 -1
  37. package/dist/components/disclosure/Dialog.d.ts +8 -8
  38. package/dist/components/disclosure/Dialog.d.ts.map +1 -1
  39. package/dist/components/disclosure/Dialog.js +64 -69
  40. package/dist/components/disclosure/Dialog.js.map +2 -2
  41. package/dist/components/disclosure/DialogContext.d.ts +4 -4
  42. package/dist/components/disclosure/DialogContext.d.ts.map +1 -1
  43. package/dist/components/disclosure/DialogContext.js.map +1 -1
  44. package/dist/components/disclosure/DialogProvider.d.ts.map +1 -1
  45. package/dist/components/disclosure/DialogProvider.js +8 -8
  46. package/dist/components/disclosure/DialogProvider.js.map +2 -2
  47. package/dist/components/disclosure/Dropdown.d.ts.map +1 -1
  48. package/dist/components/disclosure/Dropdown.js.map +1 -1
  49. package/dist/components/disclosure/Tabs.d.ts.map +1 -1
  50. package/dist/components/disclosure/Tabs.js.map +1 -1
  51. package/dist/components/display/Alert.d.ts.map +1 -1
  52. package/dist/components/display/Alert.js.map +1 -1
  53. package/dist/components/display/Card.d.ts +0 -1
  54. package/dist/components/display/Card.d.ts.map +1 -1
  55. package/dist/components/display/Card.js +1 -2
  56. package/dist/components/display/Card.js.map +1 -1
  57. package/dist/components/display/Echarts.d.ts +1 -1
  58. package/dist/components/display/Echarts.d.ts.map +1 -1
  59. package/dist/components/display/Echarts.js +2 -2
  60. package/dist/components/display/Echarts.js.map +2 -2
  61. package/dist/components/display/Link.d.ts +5 -0
  62. package/dist/components/display/Link.d.ts.map +1 -0
  63. package/dist/components/display/Link.js +26 -0
  64. package/dist/components/display/Link.js.map +6 -0
  65. package/dist/components/feedback/Progress.d.ts +3 -3
  66. package/dist/components/feedback/Progress.d.ts.map +1 -1
  67. package/dist/components/feedback/Progress.js +1 -1
  68. package/dist/components/feedback/Progress.js.map +2 -2
  69. package/dist/components/feedback/busy/BusyContainer.d.ts +13 -0
  70. package/dist/components/feedback/busy/BusyContainer.d.ts.map +1 -0
  71. package/dist/components/feedback/{loading/LoadingContainer.js → busy/BusyContainer.js} +20 -13
  72. package/dist/components/feedback/busy/BusyContainer.js.map +6 -0
  73. package/dist/components/feedback/busy/BusyContext.d.ts +11 -0
  74. package/dist/components/feedback/busy/BusyContext.d.ts.map +1 -0
  75. package/dist/components/feedback/busy/BusyContext.js +14 -0
  76. package/dist/components/feedback/busy/BusyContext.js.map +6 -0
  77. package/dist/components/feedback/busy/BusyProvider.d.ts +7 -0
  78. package/dist/components/feedback/busy/BusyProvider.d.ts.map +1 -0
  79. package/dist/components/feedback/{loading/LoadingProvider.js → busy/BusyProvider.js} +7 -7
  80. package/dist/components/feedback/busy/BusyProvider.js.map +6 -0
  81. package/dist/components/feedback/notification/NotificationBanner.d.ts.map +1 -1
  82. package/dist/components/feedback/notification/NotificationBanner.js +1 -1
  83. package/dist/components/feedback/notification/NotificationBanner.js.map +1 -1
  84. package/dist/components/feedback/notification/NotificationBell.d.ts.map +1 -1
  85. package/dist/components/feedback/notification/NotificationBell.js +4 -2
  86. package/dist/components/feedback/notification/NotificationBell.js.map +2 -2
  87. package/dist/components/feedback/notification/NotificationContext.d.ts.map +1 -1
  88. package/dist/components/feedback/notification/NotificationContext.js.map +1 -1
  89. package/dist/components/feedback/notification/NotificationProvider.d.ts.map +1 -1
  90. package/dist/components/feedback/notification/NotificationProvider.js +1 -0
  91. package/dist/components/feedback/notification/NotificationProvider.js.map +1 -1
  92. package/dist/components/form-control/Button.d.ts.map +1 -1
  93. package/dist/components/form-control/Button.js +2 -2
  94. package/dist/components/form-control/Button.js.map +1 -1
  95. package/dist/components/form-control/DropdownTrigger.styles.d.ts.map +1 -1
  96. package/dist/components/form-control/DropdownTrigger.styles.js +6 -1
  97. package/dist/components/form-control/DropdownTrigger.styles.js.map +1 -1
  98. package/dist/components/form-control/Invalid.d.ts +4 -2
  99. package/dist/components/form-control/Invalid.d.ts.map +1 -1
  100. package/dist/components/form-control/Invalid.js +81 -41
  101. package/dist/components/form-control/Invalid.js.map +2 -2
  102. package/dist/components/form-control/ThemeToggle.d.ts.map +1 -1
  103. package/dist/components/form-control/ThemeToggle.js +4 -5
  104. package/dist/components/form-control/ThemeToggle.js.map +2 -2
  105. package/dist/components/form-control/checkbox/Checkbox.d.ts +4 -2
  106. package/dist/components/form-control/checkbox/Checkbox.d.ts.map +1 -1
  107. package/dist/components/form-control/checkbox/Checkbox.js +65 -52
  108. package/dist/components/form-control/checkbox/Checkbox.js.map +2 -2
  109. package/dist/components/form-control/checkbox/Checkbox.styles.d.ts +1 -2
  110. package/dist/components/form-control/checkbox/Checkbox.styles.d.ts.map +1 -1
  111. package/dist/components/form-control/checkbox/Checkbox.styles.js +8 -10
  112. package/dist/components/form-control/checkbox/Checkbox.styles.js.map +1 -1
  113. package/dist/components/form-control/checkbox/CheckboxGroup.d.ts +9 -9
  114. package/dist/components/form-control/checkbox/CheckboxGroup.d.ts.map +1 -1
  115. package/dist/components/form-control/checkbox/CheckboxGroup.js +10 -82
  116. package/dist/components/form-control/checkbox/CheckboxGroup.js.map +2 -2
  117. package/dist/components/form-control/checkbox/Radio.d.ts +4 -2
  118. package/dist/components/form-control/checkbox/Radio.d.ts.map +1 -1
  119. package/dist/components/form-control/checkbox/Radio.js +64 -51
  120. package/dist/components/form-control/checkbox/Radio.js.map +2 -2
  121. package/dist/components/form-control/checkbox/RadioGroup.d.ts +9 -9
  122. package/dist/components/form-control/checkbox/RadioGroup.d.ts.map +1 -1
  123. package/dist/components/form-control/checkbox/RadioGroup.js +10 -77
  124. package/dist/components/form-control/checkbox/RadioGroup.js.map +2 -2
  125. package/dist/components/form-control/color-picker/ColorPicker.d.ts +8 -3
  126. package/dist/components/form-control/color-picker/ColorPicker.d.ts.map +1 -1
  127. package/dist/components/form-control/color-picker/ColorPicker.js +43 -26
  128. package/dist/components/form-control/color-picker/ColorPicker.js.map +2 -2
  129. package/dist/components/form-control/combobox/Combobox.d.ts +8 -8
  130. package/dist/components/form-control/combobox/Combobox.d.ts.map +1 -1
  131. package/dist/components/form-control/combobox/Combobox.js +72 -59
  132. package/dist/components/form-control/combobox/Combobox.js.map +2 -2
  133. package/dist/components/form-control/editor/EditorToolbar.d.ts.map +1 -1
  134. package/dist/components/form-control/editor/EditorToolbar.js +3 -2
  135. package/dist/components/form-control/editor/EditorToolbar.js.map +2 -2
  136. package/dist/components/form-control/editor/RichTextEditor.d.ts.map +1 -1
  137. package/dist/components/form-control/editor/RichTextEditor.js.map +1 -1
  138. package/dist/components/form-control/field/DatePicker.d.ts +6 -0
  139. package/dist/components/form-control/field/DatePicker.d.ts.map +1 -1
  140. package/dist/components/form-control/field/DatePicker.js +138 -117
  141. package/dist/components/form-control/field/DatePicker.js.map +2 -2
  142. package/dist/components/form-control/field/DateTimePicker.d.ts +6 -0
  143. package/dist/components/form-control/field/DateTimePicker.d.ts.map +1 -1
  144. package/dist/components/form-control/field/DateTimePicker.js +138 -115
  145. package/dist/components/form-control/field/DateTimePicker.js.map +2 -2
  146. package/dist/components/form-control/field/Field.styles.d.ts +14 -0
  147. package/dist/components/form-control/field/Field.styles.d.ts.map +1 -1
  148. package/dist/components/form-control/field/Field.styles.js +47 -3
  149. package/dist/components/form-control/field/Field.styles.js.map +1 -1
  150. package/dist/components/form-control/field/FieldPlaceholder.d.ts +7 -0
  151. package/dist/components/form-control/field/FieldPlaceholder.d.ts.map +1 -0
  152. package/dist/components/form-control/field/FieldPlaceholder.js +34 -0
  153. package/dist/components/form-control/field/FieldPlaceholder.js.map +6 -0
  154. package/dist/components/form-control/field/NumberInput.d.ts +13 -0
  155. package/dist/components/form-control/field/NumberInput.d.ts.map +1 -1
  156. package/dist/components/form-control/field/NumberInput.js +163 -111
  157. package/dist/components/form-control/field/NumberInput.js.map +2 -2
  158. package/dist/components/form-control/field/TextInput.d.ts +16 -1
  159. package/dist/components/form-control/field/TextInput.d.ts.map +1 -1
  160. package/dist/components/form-control/field/TextInput.js +177 -114
  161. package/dist/components/form-control/field/TextInput.js.map +2 -2
  162. package/dist/components/form-control/field/Textarea.d.ts +10 -0
  163. package/dist/components/form-control/field/Textarea.d.ts.map +1 -1
  164. package/dist/components/form-control/field/Textarea.js +156 -121
  165. package/dist/components/form-control/field/Textarea.js.map +2 -2
  166. package/dist/components/form-control/field/TimePicker.d.ts +10 -0
  167. package/dist/components/form-control/field/TimePicker.d.ts.map +1 -1
  168. package/dist/components/form-control/field/TimePicker.js +126 -94
  169. package/dist/components/form-control/field/TimePicker.js.map +2 -2
  170. package/dist/components/form-control/select/Select.d.ts +7 -9
  171. package/dist/components/form-control/select/Select.d.ts.map +1 -1
  172. package/dist/components/form-control/select/Select.js +71 -60
  173. package/dist/components/form-control/select/Select.js.map +2 -2
  174. package/dist/components/form-control/select/SelectItem.d.ts.map +1 -1
  175. package/dist/components/form-control/select/SelectItem.js.map +1 -1
  176. package/dist/components/form-control/state-preset/StatePreset.d.ts.map +1 -1
  177. package/dist/components/form-control/state-preset/StatePreset.js +2 -1
  178. package/dist/components/form-control/state-preset/StatePreset.js.map +2 -2
  179. package/dist/components/layout/FormGroup.d.ts.map +1 -1
  180. package/dist/components/layout/FormGroup.js.map +1 -1
  181. package/dist/components/layout/sidebar/Sidebar.d.ts.map +1 -1
  182. package/dist/components/layout/sidebar/Sidebar.js +1 -1
  183. package/dist/components/layout/sidebar/Sidebar.js.map +1 -1
  184. package/dist/components/layout/sidebar/SidebarContainer.d.ts.map +1 -1
  185. package/dist/components/layout/sidebar/SidebarContainer.js.map +1 -1
  186. package/dist/components/layout/sidebar/SidebarMenu.js +1 -1
  187. package/dist/components/layout/sidebar/SidebarMenu.js.map +1 -1
  188. package/dist/components/layout/sidebar/SidebarUser.d.ts.map +1 -1
  189. package/dist/components/layout/sidebar/SidebarUser.js +4 -4
  190. package/dist/components/layout/sidebar/SidebarUser.js.map +1 -1
  191. package/dist/components/layout/topbar/Topbar.js +1 -1
  192. package/dist/components/layout/topbar/TopbarMenu.d.ts.map +1 -1
  193. package/dist/components/layout/topbar/TopbarMenu.js.map +1 -1
  194. package/dist/components/layout/topbar/TopbarUser.d.ts.map +1 -1
  195. package/dist/components/layout/topbar/TopbarUser.js.map +1 -1
  196. package/dist/helpers/createAppStructure.d.ts.map +1 -1
  197. package/dist/helpers/createAppStructure.js +17 -12
  198. package/dist/helpers/createAppStructure.js.map +1 -1
  199. package/dist/helpers/mergeStyles.d.ts.map +1 -1
  200. package/dist/helpers/mergeStyles.js +4 -1
  201. package/dist/helpers/mergeStyles.js.map +1 -1
  202. package/dist/helpers/splitSlots.d.ts.map +1 -1
  203. package/dist/helpers/splitSlots.js.map +1 -1
  204. package/dist/hooks/createControllableSignal.d.ts.map +1 -1
  205. package/dist/hooks/createControllableSignal.js.map +1 -1
  206. package/dist/hooks/createItemTemplate.d.ts +17 -0
  207. package/dist/hooks/createItemTemplate.d.ts.map +1 -0
  208. package/dist/hooks/createItemTemplate.js +40 -0
  209. package/dist/hooks/createItemTemplate.js.map +6 -0
  210. package/dist/hooks/createPointerDrag.d.ts +13 -0
  211. package/dist/hooks/createPointerDrag.d.ts.map +1 -0
  212. package/dist/hooks/createPointerDrag.js +15 -0
  213. package/dist/hooks/createPointerDrag.js.map +6 -0
  214. package/dist/hooks/createSelectionGroup.d.ts +70 -0
  215. package/dist/hooks/createSelectionGroup.d.ts.map +1 -0
  216. package/dist/hooks/createSelectionGroup.js +141 -0
  217. package/dist/hooks/createSelectionGroup.js.map +6 -0
  218. package/dist/hooks/useClipboardValueCopy.js +3 -1
  219. package/dist/hooks/useClipboardValueCopy.js.map +1 -1
  220. package/dist/hooks/useLocalStorage.d.ts +5 -3
  221. package/dist/hooks/useLocalStorage.d.ts.map +1 -1
  222. package/dist/hooks/useLocalStorage.js.map +1 -1
  223. package/dist/hooks/usePrint.d.ts.map +1 -1
  224. package/dist/hooks/usePrint.js +5 -3
  225. package/dist/hooks/usePrint.js.map +1 -1
  226. package/dist/hooks/{createPwaUpdate.d.ts → usePwaUpdate.d.ts} +2 -2
  227. package/dist/hooks/usePwaUpdate.d.ts.map +1 -0
  228. package/dist/hooks/{createPwaUpdate.js → usePwaUpdate.js} +3 -3
  229. package/dist/hooks/usePwaUpdate.js.map +6 -0
  230. package/dist/hooks/useRouterLink.d.ts.map +1 -1
  231. package/dist/hooks/useRouterLink.js.map +1 -1
  232. package/dist/hooks/useSyncConfig.d.ts +3 -3
  233. package/dist/hooks/useSyncConfig.d.ts.map +1 -1
  234. package/dist/hooks/useSyncConfig.js +6 -7
  235. package/dist/hooks/useSyncConfig.js.map +1 -1
  236. package/dist/index.d.ts +5 -6
  237. package/dist/index.d.ts.map +1 -1
  238. package/dist/index.js +6 -7
  239. package/dist/index.js.map +1 -1
  240. package/dist/providers/ConfigContext.d.ts +2 -2
  241. package/dist/providers/ConfigContext.d.ts.map +1 -1
  242. package/dist/providers/InitializeProvider.js +5 -5
  243. package/dist/providers/InitializeProvider.js.map +2 -2
  244. package/dist/providers/ServiceClientProvider.d.ts.map +1 -1
  245. package/dist/providers/ServiceClientProvider.js.map +1 -1
  246. package/dist/providers/ThemeContext.d.ts.map +1 -1
  247. package/dist/providers/ThemeContext.js +2 -1
  248. package/dist/providers/ThemeContext.js.map +2 -2
  249. package/dist/providers/shared-data/SharedDataChangeEvent.d.ts.map +1 -1
  250. package/dist/providers/shared-data/SharedDataChangeEvent.js +1 -3
  251. package/dist/providers/shared-data/SharedDataChangeEvent.js.map +1 -1
  252. package/dist/providers/shared-data/SharedDataContext.d.ts +1 -1
  253. package/dist/providers/shared-data/SharedDataContext.d.ts.map +1 -1
  254. package/dist/providers/shared-data/SharedDataProvider.d.ts.map +1 -1
  255. package/dist/providers/shared-data/SharedDataProvider.js +6 -6
  256. package/dist/providers/shared-data/SharedDataProvider.js.map +2 -2
  257. package/dist/styles/patterns.styles.d.ts +1 -0
  258. package/dist/styles/patterns.styles.d.ts.map +1 -1
  259. package/dist/styles/patterns.styles.js +12 -1
  260. package/dist/styles/patterns.styles.js.map +1 -1
  261. package/dist/styles/tokens.styles.d.ts +2 -1
  262. package/dist/styles/tokens.styles.d.ts.map +1 -1
  263. package/dist/styles/tokens.styles.js +1 -1
  264. package/dist/styles/tokens.styles.js.map +1 -1
  265. package/docs/data-components.md +34 -5
  266. package/docs/disclosure.md +28 -8
  267. package/docs/display.md +19 -2
  268. package/docs/feedback.md +35 -12
  269. package/docs/form-controls.md +289 -33
  270. package/docs/hooks.md +21 -9
  271. package/docs/layout.md +15 -3
  272. package/docs/providers.md +120 -8
  273. package/docs/styling.md +90 -0
  274. package/package.json +3 -3
  275. package/src/components/data/Pagination.tsx +26 -22
  276. package/src/components/data/Table.tsx +1 -1
  277. package/src/components/data/calendar/Calendar.tsx +19 -5
  278. package/src/components/data/kanban/Kanban.tsx +72 -35
  279. package/src/components/data/list/List.tsx +11 -4
  280. package/src/components/data/list/ListItem.tsx +12 -2
  281. package/src/components/data/permission-table/PermissionTable.tsx +32 -5
  282. package/src/components/data/sheet/DataSheet.styles.ts +24 -6
  283. package/src/components/data/sheet/DataSheet.tsx +215 -149
  284. package/src/components/data/sheet/DataSheetColumn.tsx +5 -1
  285. package/src/components/data/sheet/DataSheetConfigDialog.tsx +27 -5
  286. package/src/components/data/sheet/sheetUtils.ts +12 -3
  287. package/src/components/data/sheet/types.ts +2 -2
  288. package/src/components/disclosure/Collapse.tsx +14 -3
  289. package/src/components/disclosure/Dialog.tsx +122 -106
  290. package/src/components/disclosure/DialogContext.ts +8 -5
  291. package/src/components/disclosure/DialogProvider.tsx +19 -7
  292. package/src/components/disclosure/Dropdown.tsx +12 -2
  293. package/src/components/disclosure/Tabs.tsx +29 -5
  294. package/src/components/display/Alert.tsx +3 -4
  295. package/src/components/display/Card.tsx +1 -2
  296. package/src/components/display/Echarts.tsx +12 -5
  297. package/src/components/display/Link.tsx +22 -0
  298. package/src/components/feedback/Progress.tsx +9 -5
  299. package/src/components/feedback/{loading/LoadingContainer.tsx → busy/BusyContainer.tsx} +52 -19
  300. package/src/components/feedback/busy/BusyContext.ts +20 -0
  301. package/src/components/feedback/{loading/LoadingProvider.tsx → busy/BusyProvider.tsx} +10 -10
  302. package/src/components/feedback/notification/NotificationBanner.tsx +14 -3
  303. package/src/components/feedback/notification/NotificationBell.tsx +21 -15
  304. package/src/components/feedback/notification/NotificationContext.ts +4 -1
  305. package/src/components/feedback/notification/NotificationProvider.tsx +4 -1
  306. package/src/components/form-control/Button.tsx +8 -3
  307. package/src/components/form-control/DropdownTrigger.styles.ts +7 -1
  308. package/src/components/form-control/Invalid.tsx +114 -48
  309. package/src/components/form-control/ThemeToggle.tsx +9 -18
  310. package/src/components/form-control/checkbox/Checkbox.styles.ts +7 -10
  311. package/src/components/form-control/checkbox/Checkbox.tsx +39 -28
  312. package/src/components/form-control/checkbox/CheckboxGroup.tsx +18 -97
  313. package/src/components/form-control/checkbox/Radio.tsx +39 -28
  314. package/src/components/form-control/checkbox/RadioGroup.tsx +18 -92
  315. package/src/components/form-control/color-picker/ColorPicker.tsx +51 -18
  316. package/src/components/form-control/combobox/Combobox.tsx +53 -35
  317. package/src/components/form-control/editor/EditorToolbar.tsx +19 -19
  318. package/src/components/form-control/editor/RichTextEditor.tsx +22 -4
  319. package/src/components/form-control/field/DatePicker.tsx +99 -93
  320. package/src/components/form-control/field/DateTimePicker.tsx +115 -96
  321. package/src/components/form-control/field/Field.styles.ts +62 -3
  322. package/src/components/form-control/field/FieldPlaceholder.tsx +18 -0
  323. package/src/components/form-control/field/NumberInput.tsx +136 -84
  324. package/src/components/form-control/field/TextInput.tsx +135 -88
  325. package/src/components/form-control/field/Textarea.tsx +126 -99
  326. package/src/components/form-control/field/TimePicker.tsx +101 -71
  327. package/src/components/form-control/select/Select.tsx +75 -42
  328. package/src/components/form-control/select/SelectItem.tsx +3 -1
  329. package/src/components/form-control/state-preset/StatePreset.tsx +41 -22
  330. package/src/components/layout/FormGroup.tsx +11 -2
  331. package/src/components/layout/sidebar/Sidebar.tsx +3 -2
  332. package/src/components/layout/sidebar/SidebarContainer.tsx +8 -1
  333. package/src/components/layout/sidebar/SidebarMenu.tsx +8 -2
  334. package/src/components/layout/sidebar/SidebarUser.tsx +12 -7
  335. package/src/components/layout/topbar/Topbar.tsx +1 -1
  336. package/src/components/layout/topbar/TopbarMenu.tsx +27 -5
  337. package/src/components/layout/topbar/TopbarUser.tsx +5 -1
  338. package/src/helpers/createAppStructure.ts +29 -15
  339. package/src/helpers/mergeStyles.ts +6 -2
  340. package/src/helpers/splitSlots.ts +4 -1
  341. package/src/hooks/createControllableSignal.ts +2 -1
  342. package/src/hooks/createItemTemplate.tsx +42 -0
  343. package/src/hooks/createPointerDrag.ts +28 -0
  344. package/src/hooks/createSelectionGroup.tsx +235 -0
  345. package/src/hooks/useClipboardValueCopy.ts +5 -2
  346. package/src/hooks/useLocalStorage.ts +11 -5
  347. package/src/hooks/usePrint.ts +9 -4
  348. package/src/hooks/{createPwaUpdate.ts → usePwaUpdate.ts} +1 -1
  349. package/src/hooks/useRouterLink.ts +3 -1
  350. package/src/hooks/useSyncConfig.ts +9 -13
  351. package/src/index.ts +6 -7
  352. package/src/providers/ConfigContext.ts +2 -2
  353. package/src/providers/InitializeProvider.tsx +4 -4
  354. package/src/providers/ServiceClientProvider.tsx +14 -3
  355. package/src/providers/ThemeContext.tsx +12 -3
  356. package/src/providers/shared-data/SharedDataChangeEvent.ts +4 -3
  357. package/src/providers/shared-data/SharedDataContext.ts +1 -1
  358. package/src/providers/shared-data/SharedDataProvider.tsx +13 -8
  359. package/src/styles/patterns.styles.ts +13 -1
  360. package/src/styles/tokens.styles.ts +2 -1
  361. package/tailwind.config.ts +9 -0
  362. package/tailwind.css +1 -1
  363. package/dist/components/display/Card.css +0 -15
  364. package/dist/components/feedback/loading/LoadingContainer.d.ts +0 -12
  365. package/dist/components/feedback/loading/LoadingContainer.d.ts.map +0 -1
  366. package/dist/components/feedback/loading/LoadingContainer.js.map +0 -6
  367. package/dist/components/feedback/loading/LoadingContext.d.ts +0 -11
  368. package/dist/components/feedback/loading/LoadingContext.d.ts.map +0 -1
  369. package/dist/components/feedback/loading/LoadingContext.js +0 -14
  370. package/dist/components/feedback/loading/LoadingContext.js.map +0 -6
  371. package/dist/components/feedback/loading/LoadingProvider.d.ts +0 -7
  372. package/dist/components/feedback/loading/LoadingProvider.d.ts.map +0 -1
  373. package/dist/components/feedback/loading/LoadingProvider.js.map +0 -6
  374. package/dist/hooks/createPwaUpdate.d.ts.map +0 -1
  375. package/dist/hooks/createPwaUpdate.js.map +0 -6
  376. package/src/components/display/Card.css +0 -15
  377. package/src/components/feedback/loading/LoadingContext.ts +0 -20
  378. /package/dist/components/feedback/{loading/LoadingContainer.css → busy/BusyContainer.css} +0 -0
  379. /package/src/components/feedback/{loading/LoadingContainer.css → busy/BusyContainer.css} +0 -0
@@ -0,0 +1,22 @@
1
+ import { type JSX, type ParentComponent, splitProps } from "solid-js";
2
+ import clsx from "clsx";
3
+ import { twMerge } from "tailwind-merge";
4
+
5
+ export interface LinkProps extends JSX.AnchorHTMLAttributes<HTMLAnchorElement> {}
6
+
7
+ const baseClass = clsx(
8
+ "text-primary-600",
9
+ "dark:text-primary-400",
10
+ "hover:underline",
11
+ "cursor-pointer",
12
+ );
13
+
14
+ export const Link: ParentComponent<LinkProps> = (props) => {
15
+ const [local, rest] = splitProps(props, ["children", "class"]);
16
+
17
+ return (
18
+ <a class={twMerge(baseClass, local.class)} {...rest}>
19
+ {local.children}
20
+ </a>
21
+ );
22
+ };
@@ -1,15 +1,19 @@
1
1
  import { type JSX, type ParentComponent, Show, splitProps } from "solid-js";
2
2
  import clsx from "clsx";
3
3
  import { twMerge } from "tailwind-merge";
4
- import { type SemanticTheme, themeTokens } from "../../styles/tokens.styles";
4
+ import {
5
+ type ComponentSizeCompact,
6
+ type SemanticTheme,
7
+ themeTokens,
8
+ } from "../../styles/tokens.styles";
5
9
 
6
10
  export type ProgressTheme = SemanticTheme;
7
- export type ProgressSize = "sm" | "lg";
8
11
 
9
12
  export interface ProgressProps extends JSX.HTMLAttributes<HTMLDivElement> {
13
+ /** 진행률 (0~1 범위, 0 = 0%, 1 = 100%) */
10
14
  value: number;
11
15
  theme?: ProgressTheme;
12
- size?: ProgressSize;
16
+ size?: ComponentSizeCompact;
13
17
  inset?: boolean;
14
18
  }
15
19
 
@@ -21,7 +25,7 @@ const baseClass = clsx(
21
25
  "border border-base-200 dark:border-base-700",
22
26
  );
23
27
 
24
- const sizeClasses: Record<"default" | ProgressSize, string> = {
28
+ const sizeClasses: Record<"default" | ComponentSizeCompact, string> = {
25
29
  default: "py-1 px-2",
26
30
  sm: "py-0.5 px-2",
27
31
  lg: "py-2 px-3",
@@ -46,7 +50,7 @@ export const Progress: ParentComponent<ProgressProps> = (props) => {
46
50
  return clsx("absolute left-0 top-0 h-full", "z-[1]", "transition-all", barThemeClasses[theme]);
47
51
  };
48
52
 
49
- const getPercentText = () => (local.value * 100).toFixed(2) + "%";
53
+ const getPercentText = () => (Math.max(0, Math.min(1, local.value)) * 100).toFixed(2) + "%";
50
54
 
51
55
  return (
52
56
  <div data-progress class={getClassName()} {...rest}>
@@ -1,13 +1,22 @@
1
- import { type ParentComponent, type JSX, splitProps, createEffect, onCleanup, Show, useContext } from "solid-js";
1
+ import {
2
+ type ParentComponent,
3
+ type JSX,
4
+ splitProps,
5
+ createEffect,
6
+ onCleanup,
7
+ Show,
8
+ useContext,
9
+ } from "solid-js";
2
10
  import clsx from "clsx";
3
11
  import { twMerge } from "tailwind-merge";
4
- import { LoadingContext, type LoadingVariant } from "./LoadingContext";
12
+ import { BusyContext, type BusyVariant } from "./BusyContext";
5
13
  import { createMountTransition } from "../../../hooks/createMountTransition";
6
- import "./LoadingContainer.css";
14
+ import "./BusyContainer.css";
7
15
 
8
- export interface LoadingContainerProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, "children"> {
16
+ export interface BusyContainerProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, "children"> {
9
17
  busy?: boolean;
10
- variant?: LoadingVariant;
18
+ ready?: boolean;
19
+ variant?: BusyVariant;
11
20
  message?: string;
12
21
  progressPercent?: number;
13
22
  children?: JSX.Element;
@@ -46,18 +55,28 @@ const progressBarClass = clsx(
46
55
 
47
56
  const barIndicatorClass = clsx("absolute left-0 top-0", "h-1 w-full", "bg-white dark:bg-base-800");
48
57
 
49
- export const LoadingContainer: ParentComponent<LoadingContainerProps> = (props) => {
50
- const [local, rest] = splitProps(props, ["busy", "variant", "message", "progressPercent", "class", "children"]);
58
+ export const BusyContainer: ParentComponent<BusyContainerProps> = (props) => {
59
+ const [local, rest] = splitProps(props, [
60
+ "busy",
61
+ "ready",
62
+ "variant",
63
+ "message",
64
+ "progressPercent",
65
+ "class",
66
+ "children",
67
+ ]);
51
68
 
52
- const loadingCtx = useContext(LoadingContext);
53
- const currVariant = (): LoadingVariant => local.variant ?? loadingCtx?.variant() ?? "spinner";
69
+ const busyCtx = useContext(BusyContext);
70
+ const currVariant = (): BusyVariant => local.variant ?? busyCtx?.variant() ?? "spinner";
54
71
 
55
72
  // 애니메이션 상태 (mount transition)
56
- const { mounted, animating, unmount } = createMountTransition(() => !!local.busy);
73
+ const { mounted, animating, unmount } = createMountTransition(
74
+ () => local.ready === false || !!local.busy,
75
+ );
57
76
 
58
77
  const handleTransitionEnd = (e: TransitionEvent) => {
59
78
  if (e.propertyName !== "opacity") return;
60
- if (!local.busy) {
79
+ if (local.ready !== false && !local.busy) {
61
80
  unmount();
62
81
  }
63
82
  };
@@ -67,18 +86,23 @@ export const LoadingContainer: ParentComponent<LoadingContainerProps> = (props)
67
86
 
68
87
  createEffect(() => {
69
88
  const handleKeyDownCapture = (e: KeyboardEvent) => {
70
- if (local.busy) {
89
+ if (local.ready === false || local.busy) {
71
90
  e.preventDefault();
72
91
  e.stopPropagation();
73
92
  }
74
93
  };
75
94
 
76
95
  containerRef.addEventListener("keydown", handleKeyDownCapture, { capture: true });
77
- onCleanup(() => containerRef.removeEventListener("keydown", handleKeyDownCapture, { capture: true }));
96
+ onCleanup(() =>
97
+ containerRef.removeEventListener("keydown", handleKeyDownCapture, { capture: true }),
98
+ );
78
99
  });
79
100
 
80
101
  const screenClass = () =>
81
- clsx(screenBaseClass, animating() ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0");
102
+ clsx(
103
+ screenBaseClass,
104
+ animating() ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0",
105
+ );
82
106
 
83
107
  // spinner: 슬라이드 다운 애니메이션
84
108
  const rectClass = () => {
@@ -97,16 +121,22 @@ export const LoadingContainer: ParentComponent<LoadingContainerProps> = (props)
97
121
  <Show when={currVariant() === "spinner"}>
98
122
  <div class={spinnerClass} />
99
123
  </Show>
100
- <Show when={currVariant() === "bar" && local.busy}>
124
+ <Show when={currVariant() === "bar" && (local.ready === false || local.busy)}>
101
125
  <div class={barIndicatorClass}>
102
126
  <div
103
- class={clsx("absolute left-0 top-0 h-1 w-full origin-left", "bg-primary-500 dark:bg-primary-400")}
127
+ class={clsx(
128
+ "absolute left-0 top-0 h-1 w-full origin-left",
129
+ "bg-primary-500 dark:bg-primary-400",
130
+ )}
104
131
  style={{
105
132
  animation: "sd-busy-bar-before 2s infinite ease-in",
106
133
  }}
107
134
  />
108
135
  <div
109
- class={clsx("absolute left-0 top-0 h-1 w-full origin-left", "bg-white dark:bg-base-800")}
136
+ class={clsx(
137
+ "absolute left-0 top-0 h-1 w-full origin-left",
138
+ "bg-white dark:bg-base-800",
139
+ )}
110
140
  style={{
111
141
  animation: "sd-busy-bar-after 2s infinite ease-out",
112
142
  }}
@@ -121,12 +151,15 @@ export const LoadingContainer: ParentComponent<LoadingContainerProps> = (props)
121
151
  </div>
122
152
  <Show when={local.progressPercent != null}>
123
153
  <div class={progressTrackClass}>
124
- <div class={progressBarClass} style={{ transform: `scaleX(${(local.progressPercent ?? 0) / 100})` }} />
154
+ <div
155
+ class={progressBarClass}
156
+ style={{ transform: `scaleX(${(local.progressPercent ?? 0) / 100})` }}
157
+ />
125
158
  </div>
126
159
  </Show>
127
160
  </div>
128
161
  </Show>
129
- {local.children}
162
+ <Show when={local.ready !== false}>{local.children}</Show>
130
163
  </div>
131
164
  );
132
165
  };
@@ -0,0 +1,20 @@
1
+ import { createContext, useContext, type Accessor } from "solid-js";
2
+
3
+ export type BusyVariant = "spinner" | "bar";
4
+
5
+ export interface BusyContextValue {
6
+ variant: Accessor<BusyVariant>;
7
+ show: (message?: string) => void;
8
+ hide: () => void;
9
+ setProgress: (percent: number | undefined) => void;
10
+ }
11
+
12
+ export const BusyContext = createContext<BusyContextValue>();
13
+
14
+ export function useBusy(): BusyContextValue {
15
+ const context = useContext(BusyContext);
16
+ if (!context) {
17
+ throw new Error("useBusy는 BusyProvider 내부에서만 사용할 수 있습니다");
18
+ }
19
+ return context;
20
+ }
@@ -1,21 +1,21 @@
1
1
  import { type ParentComponent, createSignal } from "solid-js";
2
2
  import { Portal } from "solid-js/web";
3
3
  import clsx from "clsx";
4
- import { LoadingContext, type LoadingContextValue, type LoadingVariant } from "./LoadingContext";
5
- import { LoadingContainer } from "./LoadingContainer";
4
+ import { BusyContext, type BusyContextValue, type BusyVariant } from "./BusyContext";
5
+ import { BusyContainer } from "./BusyContainer";
6
6
 
7
7
  const overlayClass = clsx("fixed left-0 top-0", "h-screen w-screen", "overflow-hidden");
8
8
 
9
- export interface LoadingProviderProps {
10
- variant?: LoadingVariant;
9
+ export interface BusyProviderProps {
10
+ variant?: BusyVariant;
11
11
  }
12
12
 
13
- export const LoadingProvider: ParentComponent<LoadingProviderProps> = (props) => {
13
+ export const BusyProvider: ParentComponent<BusyProviderProps> = (props) => {
14
14
  const [busyCount, setBusyCount] = createSignal(0);
15
15
  const [message, setMessage] = createSignal<string | undefined>();
16
16
  const [progress, setProgress] = createSignal<number | undefined>();
17
17
 
18
- const variant = (): LoadingVariant => props.variant ?? "spinner";
18
+ const variant = (): BusyVariant => props.variant ?? "spinner";
19
19
 
20
20
  const show = (msg?: string): void => {
21
21
  setBusyCount((c) => c + 1);
@@ -33,7 +33,7 @@ export const LoadingProvider: ParentComponent<LoadingProviderProps> = (props) =>
33
33
  }
34
34
  };
35
35
 
36
- const contextValue: LoadingContextValue = {
36
+ const contextValue: BusyContextValue = {
37
37
  variant,
38
38
  show,
39
39
  hide,
@@ -41,10 +41,10 @@ export const LoadingProvider: ParentComponent<LoadingProviderProps> = (props) =>
41
41
  };
42
42
 
43
43
  return (
44
- <LoadingContext.Provider value={contextValue}>
44
+ <BusyContext.Provider value={contextValue}>
45
45
  {props.children}
46
46
  <Portal>
47
- <LoadingContainer
47
+ <BusyContainer
48
48
  busy={busyCount() > 0}
49
49
  variant={variant()}
50
50
  message={message()}
@@ -53,6 +53,6 @@ export const LoadingProvider: ParentComponent<LoadingProviderProps> = (props) =>
53
53
  style={{ "pointer-events": busyCount() > 0 ? "auto" : "none" }}
54
54
  />
55
55
  </Portal>
56
- </LoadingContext.Provider>
56
+ </BusyContext.Provider>
57
57
  );
58
58
  };
@@ -33,7 +33,13 @@ const themeClasses: Record<string, string> = {
33
33
  const contentClass = clsx("flex flex-col", "gap-0.5", "min-w-0");
34
34
  const messageClass = clsx("text-sm", "opacity-90", "overflow-auto");
35
35
  const actionsClass = clsx("flex items-center", "gap-2", "shrink-0");
36
- const actionButtonClass = clsx("rounded", "bg-white/20", "px-3 py-1", "text-sm", "hover:bg-white/30");
36
+ const actionButtonClass = clsx(
37
+ "rounded",
38
+ "bg-white/20",
39
+ "px-3 py-1",
40
+ "text-sm",
41
+ "hover:bg-white/30",
42
+ );
37
43
  const dismissButtonClass = clsx("rounded", "p-1", "hover:bg-white/20");
38
44
 
39
45
  export const NotificationBanner: Component = () => {
@@ -59,7 +65,7 @@ export const NotificationBanner: Component = () => {
59
65
  class={clsx(baseClass, themeClasses[item().theme])}
60
66
  >
61
67
  <div class={contentClass}>
62
- <span class="font-semibold">{item().title}</span>
68
+ <span class="font-bold">{item().title}</span>
63
69
  <Show when={item().message}>
64
70
  <pre class={messageClass}>{item().message}</pre>
65
71
  </Show>
@@ -70,7 +76,12 @@ export const NotificationBanner: Component = () => {
70
76
  {item().action!.label}
71
77
  </button>
72
78
  </Show>
73
- <button type="button" aria-label="알림 닫기" class={dismissButtonClass} onClick={handleDismiss}>
79
+ <button
80
+ type="button"
81
+ aria-label="알림 닫기"
82
+ class={dismissButtonClass}
83
+ onClick={handleDismiss}
84
+ >
74
85
  <Icon icon={IconX} size="1.25em" />
75
86
  </button>
76
87
  </div>
@@ -1,26 +1,18 @@
1
1
  import { type Component, createSignal, For, Show } from "solid-js";
2
2
  import { IconBell } from "@tabler/icons-solidjs";
3
3
  import clsx from "clsx";
4
+ import { twMerge } from "tailwind-merge";
4
5
  import { useNotification } from "./NotificationContext";
5
6
  import { Dropdown } from "../../disclosure/Dropdown";
6
7
  import { Icon } from "../../display/Icon";
7
8
  import { NotificationBanner } from "./NotificationBanner";
9
+ import { iconButtonBase } from "../../../styles/patterns.styles";
8
10
 
9
11
  export interface NotificationBellProps {
10
12
  showBanner?: boolean;
11
13
  }
12
14
 
13
- const buttonClass = clsx(
14
- "relative",
15
- "p-2",
16
- "rounded-full",
17
- "hover:bg-base-100",
18
- "dark:hover:bg-base-700",
19
- "transition-colors",
20
- "focus:outline-none",
21
- "focus-visible:ring-2",
22
- "focus-visible:ring-primary-500",
23
- );
15
+ const buttonClass = twMerge(iconButtonBase, "relative", "p-2", "rounded-full");
24
16
 
25
17
  const badgeClass = clsx(
26
18
  "absolute",
@@ -99,18 +91,32 @@ export const NotificationBell: Component<NotificationBellProps> = (props) => {
99
91
  </Show>
100
92
  </button>
101
93
 
102
- <Dropdown triggerRef={() => buttonRef} open={open()} onOpenChange={handleOpenChange} maxHeight={400} class="w-80">
94
+ <Dropdown
95
+ triggerRef={() => buttonRef}
96
+ open={open()}
97
+ onOpenChange={handleOpenChange}
98
+ maxHeight={400}
99
+ class="w-80"
100
+ >
103
101
  <div class="p-2">
104
102
  <div class={dropdownHeaderClass}>
105
- <span class="font-semibold">알림</span>
103
+ <span class="font-bold">알림</span>
106
104
  <Show when={notification.items().length > 0}>
107
- <button type="button" data-notification-clear class={clearButtonClass} onClick={handleClear}>
105
+ <button
106
+ type="button"
107
+ data-notification-clear
108
+ class={clearButtonClass}
109
+ onClick={handleClear}
110
+ >
108
111
  전체 삭제
109
112
  </button>
110
113
  </Show>
111
114
  </div>
112
115
 
113
- <Show when={notification.items().length > 0} fallback={<div class={emptyClass}>알림이 없습니다</div>}>
116
+ <Show
117
+ when={notification.items().length > 0}
118
+ fallback={<div class={emptyClass}>알림이 없습니다</div>}
119
+ >
114
120
  <div class={listClass}>
115
121
  <For each={[...notification.items()].reverse()}>
116
122
  {(item) => (
@@ -38,7 +38,10 @@ export interface NotificationContextValue {
38
38
  danger: (title: string, message?: string, options?: NotificationOptions) => string;
39
39
 
40
40
  // 에러 처리 (danger 알림 + 로깅)
41
- try: <TResult>(fn: () => Promise<TResult> | TResult, header?: string) => Promise<TResult | undefined>;
41
+ try: <TResult>(
42
+ fn: () => Promise<TResult> | TResult,
43
+ header?: string,
44
+ ) => Promise<TResult | undefined>;
42
45
 
43
46
  // 알림 수정
44
47
  update: (
@@ -125,6 +125,7 @@ export const NotificationProvider: ParentComponent = (props) => {
125
125
  const latest = latestUnread();
126
126
  if (latest) {
127
127
  setDismissedBannerId(latest.id);
128
+ markAsRead(latest.id);
128
129
  }
129
130
  };
130
131
 
@@ -154,7 +155,9 @@ export const NotificationProvider: ParentComponent = (props) => {
154
155
  <NotificationContext.Provider value={contextValue}>
155
156
  {/* 스크린 리더용 Live Region */}
156
157
  <div role="status" aria-live="polite" aria-atomic="true" class="sr-only">
157
- <Show when={latestUnread()}>{(item) => `알림: ${item().title} ${item().message ?? ""}`}</Show>
158
+ <Show when={latestUnread()}>
159
+ {(item) => `알림: ${item().title} ${item().message ?? ""}`}
160
+ </Show>
158
161
  </div>
159
162
  {props.children}
160
163
  </NotificationContext.Provider>
@@ -2,7 +2,12 @@ import { type JSX, type ParentComponent, splitProps } from "solid-js";
2
2
  import clsx from "clsx";
3
3
  import { twMerge } from "tailwind-merge";
4
4
  import { ripple } from "../../directives/ripple";
5
- import { themeTokens, type SemanticTheme, type ComponentSize, disabledOpacity } from "../../styles/tokens.styles";
5
+ import {
6
+ themeTokens,
7
+ type SemanticTheme,
8
+ type ComponentSize,
9
+ disabledOpacity,
10
+ } from "../../styles/tokens.styles";
6
11
 
7
12
  // Directive 사용 선언 (TypeScript용)
8
13
  void ripple;
@@ -18,7 +23,7 @@ const baseClass = clsx(
18
23
  "justify-center",
19
24
  "text-center",
20
25
  "cursor-pointer",
21
- "transition-colors",
26
+ "transition",
22
27
  "rounded",
23
28
  "focus:outline-none",
24
29
  "focus-visible:ring-2",
@@ -30,7 +35,7 @@ const themeClasses = Object.fromEntries(
30
35
  Object.entries(themeTokens).map(([theme, t]) => [
31
36
  theme,
32
37
  {
33
- solid: clsx(t.solid, t.solidHover),
38
+ solid: clsx(t.solid, t.solidHover, "shadow-md hover:shadow-lg"),
34
39
  outline: clsx("bg-transparent", t.hoverBg, t.text, t.border),
35
40
  ghost: clsx("bg-transparent", t.hoverBg, t.text),
36
41
  },
@@ -1,6 +1,12 @@
1
1
  import clsx from "clsx";
2
2
  import { twMerge } from "tailwind-merge";
3
- import { borderDefault, type ComponentSize, paddingLg, paddingSm, paddingXl } from "../../styles/tokens.styles";
3
+ import {
4
+ borderDefault,
5
+ type ComponentSize,
6
+ paddingLg,
7
+ paddingSm,
8
+ paddingXl,
9
+ } from "../../styles/tokens.styles";
4
10
  import { insetBase, insetFocusOutlineSelf } from "../../styles/patterns.styles";
5
11
 
6
12
  export const triggerBaseClass = clsx(
@@ -1,65 +1,131 @@
1
- import { type ParentComponent, createEffect, splitProps } from "solid-js";
2
- import clsx from "clsx";
3
- import { twMerge } from "tailwind-merge";
1
+ import { type ParentComponent, children, createEffect, createSignal, onCleanup } from "solid-js";
4
2
  import "@simplysm/core-browser";
5
3
 
6
4
  export interface InvalidProps {
7
5
  /** Validation error message. Non-empty = invalid. */
8
6
  message?: string;
9
- /** Custom class */
10
- class?: string;
7
+ /** Visual indicator variant */
8
+ variant?: "border" | "dot";
9
+ /** When true, visual display only appears after target loses focus */
10
+ touchMode?: boolean;
11
11
  }
12
12
 
13
- const anchorClass = clsx(
14
- "relative inline-block",
15
- "size-0 align-top",
16
- "overflow-visible",
17
- "pointer-events-none select-none",
18
- );
13
+ export const Invalid: ParentComponent<InvalidProps> = (props) => {
14
+ const hiddenInputEl = document.createElement("input");
15
+ hiddenInputEl.type = "text";
16
+ hiddenInputEl.style.cssText =
17
+ "position:absolute; bottom:0; left:50%; width:1px; height:1px; opacity:0; pointer-events:none; z-index:-10;";
18
+ hiddenInputEl.autocomplete = "off";
19
+ hiddenInputEl.tabIndex = -1;
20
+ hiddenInputEl.setAttribute("aria-hidden", "true");
19
21
 
20
- const indicatorClass = clsx("absolute left-0.5 top-0.5", "size-1.5 rounded-full", "bg-danger-500");
22
+ const [touched, setTouched] = createSignal(false);
21
23
 
22
- const hiddenInputClass = clsx(
23
- "absolute bottom-0 left-0.5",
24
- "size-px opacity-0",
25
- "pointer-events-none -z-10",
26
- "select-none",
27
- );
24
+ const resolved = children(() => props.children);
28
25
 
29
- export const Invalid: ParentComponent<InvalidProps> = (props) => {
30
- const [local, rest] = splitProps(props, ["message", "class", "children"]);
26
+ // message 변경 setCustomValidity 반응형 업데이트 (touchMode 무관하게 항상)
27
+ createEffect(() => {
28
+ const msg = props.message ?? "";
29
+ hiddenInputEl.setCustomValidity(msg);
30
+ });
31
+
32
+ // target에 relative 설정 + hidden input을 target 내부에 삽입
33
+ createEffect(() => {
34
+ const targetEl = resolved.toArray().find((el): el is HTMLElement => el instanceof HTMLElement);
35
+ if (!targetEl) return;
36
+
37
+ const computedPosition = getComputedStyle(targetEl).position;
38
+ if (computedPosition === "static") {
39
+ targetEl.style.position = "relative";
40
+ }
41
+
42
+ targetEl.appendChild(hiddenInputEl);
31
43
 
32
- let hiddenInputEl!: HTMLInputElement;
44
+ onCleanup(() => {
45
+ if (hiddenInputEl.parentElement === targetEl) {
46
+ targetEl.removeChild(hiddenInputEl);
47
+ }
48
+ });
49
+ });
33
50
 
34
- // message 변경 시 setCustomValidity 반응형 업데이트
51
+ // 시각적 표시 처리
35
52
  createEffect(() => {
36
- const msg = local.message ?? "";
37
- hiddenInputEl.setCustomValidity(msg);
53
+ const variant = props.variant ?? "dot";
54
+ const message = props.message ?? "";
55
+ const touchMode = props.touchMode ?? false;
56
+ const isTouched = touched();
57
+
58
+ const targetEl = resolved.toArray().find((el): el is HTMLElement => el instanceof HTMLElement);
59
+
60
+ if (!targetEl) return;
61
+
62
+ const shouldShow = message !== "" && (!touchMode || isTouched);
63
+
64
+ if (variant === "border") {
65
+ if (shouldShow) {
66
+ targetEl.classList.add("border-danger-500");
67
+ } else {
68
+ targetEl.classList.remove("border-danger-500");
69
+ }
70
+
71
+ onCleanup(() => {
72
+ targetEl.classList.remove("border-danger-500");
73
+ });
74
+ } else {
75
+ // variant === "dot"
76
+ const existingDot = targetEl.querySelector("[data-invalid-dot]");
77
+ if (existingDot) {
78
+ existingDot.remove();
79
+ }
80
+
81
+ if (shouldShow) {
82
+ const dot = document.createElement("span");
83
+ dot.setAttribute("data-invalid-dot", "");
84
+ dot.style.cssText =
85
+ "position:absolute; top:2px; right:2px; width:6px; height:6px; border-radius:50%; background:red; pointer-events:none;";
86
+ targetEl.appendChild(dot);
87
+ }
88
+
89
+ onCleanup(() => {
90
+ const dot = targetEl.querySelector("[data-invalid-dot]");
91
+ if (dot) {
92
+ dot.remove();
93
+ }
94
+ });
95
+ }
38
96
  });
39
97
 
40
- const handleHiddenInputFocus = (e: FocusEvent) => {
41
- const container = (e.currentTarget as HTMLElement).parentElement;
42
- if (!container) return;
43
- const focusable = container.findFirstFocusableChild();
44
- if (focusable && focusable !== e.currentTarget) {
45
- focusable.focus();
98
+ // touchMode: target에 focusout 이벤트 등록하여 touched 상태 추적
99
+ createEffect(() => {
100
+ if (!(props.touchMode ?? false)) return;
101
+
102
+ const targetEl = resolved.toArray().find((el): el is HTMLElement => el instanceof HTMLElement);
103
+
104
+ if (!targetEl) return;
105
+
106
+ const handleFocusOut = () => {
107
+ setTouched(true);
108
+ };
109
+
110
+ targetEl.addEventListener("focusout", handleFocusOut);
111
+
112
+ onCleanup(() => {
113
+ targetEl.removeEventListener("focusout", handleFocusOut);
114
+ });
115
+ });
116
+
117
+ // hidden input 포커스 시 target의 focusable child로 리디렉션
118
+ hiddenInputEl.addEventListener("focus", () => {
119
+ const targetEl = resolved.toArray().find((el): el is HTMLElement => el instanceof HTMLElement);
120
+
121
+ if (targetEl) {
122
+ const focusable =
123
+ targetEl.findFirstFocusableChild() ?? (targetEl.tabIndex >= 0 ? targetEl : undefined);
124
+ if (focusable && focusable !== hiddenInputEl) {
125
+ focusable.focus();
126
+ }
46
127
  }
47
- };
48
-
49
- return (
50
- <div {...rest} class={twMerge("inline", local.class)}>
51
- <span class={anchorClass}>
52
- <span class={indicatorClass} style={{ display: (local.message ?? "") !== "" ? "block" : "none" }} />
53
- </span>
54
- {local.children}
55
- <input
56
- ref={hiddenInputEl}
57
- type="text"
58
- class={hiddenInputClass}
59
- tabIndex={-1}
60
- aria-hidden="true"
61
- onFocus={handleHiddenInputFocus}
62
- />
63
- </div>
64
- );
128
+ });
129
+
130
+ return <>{resolved()}</>;
65
131
  };