@happyvertical/smrt-ui 0.52.0 → 0.53.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 (208) hide show
  1. package/AGENTS.md +11 -1
  2. package/README.md +300 -2
  3. package/dist/components/data/DataTable.svelte +60 -15
  4. package/dist/components/data/DataTable.svelte.d.ts.map +1 -1
  5. package/dist/components/data/DataTableResponsive.d.ts +4 -0
  6. package/dist/components/data/DataTableResponsive.d.ts.map +1 -0
  7. package/dist/components/data/DataTableResponsive.js +22 -0
  8. package/dist/components/data/__tests__/DataTableResponsive.test.js +80 -0
  9. package/dist/components/data/__tests__/responsive-table.fixture.svelte +29 -0
  10. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts +7 -0
  11. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts.map +1 -0
  12. package/dist/components/data/types.d.ts +8 -0
  13. package/dist/components/data/types.d.ts.map +1 -1
  14. package/dist/components/display/DateDisplay.svelte +76 -14
  15. package/dist/components/display/DateDisplay.svelte.d.ts +2 -0
  16. package/dist/components/display/DateDisplay.svelte.d.ts.map +1 -1
  17. package/dist/components/display/Icon.svelte +3 -16
  18. package/dist/components/display/Icon.svelte.d.ts +0 -9
  19. package/dist/components/display/Icon.svelte.d.ts.map +1 -1
  20. package/dist/components/display/StatusBadge.svelte +29 -2
  21. package/dist/components/display/StatusBadge.svelte.d.ts +4 -2
  22. package/dist/components/display/StatusBadge.svelte.d.ts.map +1 -1
  23. package/dist/components/display/__tests__/DateDisplay.test.js +82 -1
  24. package/dist/components/display/__tests__/Icon.test.js +132 -2
  25. package/dist/components/display/__tests__/StatusBadge.test.js +78 -0
  26. package/dist/components/display/icons.svelte.d.ts +11 -0
  27. package/dist/components/display/icons.svelte.d.ts.map +1 -0
  28. package/dist/components/display/icons.svelte.js +60 -0
  29. package/dist/components/display/index.d.ts +3 -1
  30. package/dist/components/display/index.d.ts.map +1 -1
  31. package/dist/components/display/index.js +1 -0
  32. package/dist/components/display/types.d.ts +2 -0
  33. package/dist/components/display/types.d.ts.map +1 -1
  34. package/dist/components/feedback/ConfirmDialog.svelte +59 -48
  35. package/dist/components/feedback/ConfirmDialog.svelte.d.ts +14 -2
  36. package/dist/components/feedback/ConfirmDialog.svelte.d.ts.map +1 -1
  37. package/dist/components/feedback/ToastViewport.svelte +156 -8
  38. package/dist/components/feedback/ToastViewport.svelte.d.ts +5 -1
  39. package/dist/components/feedback/ToastViewport.svelte.d.ts.map +1 -1
  40. package/dist/components/feedback/__tests__/ConfirmDialog.test.js +38 -20
  41. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte +9 -0
  42. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts +19 -0
  43. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts.map +1 -0
  44. package/dist/components/feedback/__tests__/toast.test.js +142 -1
  45. package/dist/components/feedback/index.d.ts +1 -1
  46. package/dist/components/feedback/index.d.ts.map +1 -1
  47. package/dist/components/feedback/toast.d.ts +13 -1
  48. package/dist/components/feedback/toast.d.ts.map +1 -1
  49. package/dist/components/feedback/toast.js +51 -10
  50. package/dist/components/forms/CameraCapture.svelte +619 -0
  51. package/dist/components/forms/CameraCapture.svelte.d.ts +42 -0
  52. package/dist/components/forms/CameraCapture.svelte.d.ts.map +1 -0
  53. package/dist/components/forms/Checkbox.svelte +8 -1
  54. package/dist/components/forms/Checkbox.svelte.d.ts +2 -0
  55. package/dist/components/forms/Checkbox.svelte.d.ts.map +1 -1
  56. package/dist/components/forms/Combobox.svelte +40 -0
  57. package/dist/components/forms/Combobox.svelte.d.ts.map +1 -1
  58. package/dist/components/forms/Form.svelte +5 -0
  59. package/dist/components/forms/Form.svelte.d.ts +1 -0
  60. package/dist/components/forms/Form.svelte.d.ts.map +1 -1
  61. package/dist/components/forms/Input.svelte +8 -1
  62. package/dist/components/forms/Input.svelte.d.ts +2 -0
  63. package/dist/components/forms/Input.svelte.d.ts.map +1 -1
  64. package/dist/components/forms/Listbox.svelte +39 -1
  65. package/dist/components/forms/Listbox.svelte.d.ts.map +1 -1
  66. package/dist/components/forms/MultiSelect.svelte +41 -1
  67. package/dist/components/forms/MultiSelect.svelte.d.ts.map +1 -1
  68. package/dist/components/forms/Radio.svelte +1 -0
  69. package/dist/components/forms/RadioGroup.svelte +7 -1
  70. package/dist/components/forms/RadioGroup.svelte.d.ts +2 -0
  71. package/dist/components/forms/RadioGroup.svelte.d.ts.map +1 -1
  72. package/dist/components/forms/SegmentedControl.svelte +152 -11
  73. package/dist/components/forms/SegmentedControl.svelte.d.ts +4 -0
  74. package/dist/components/forms/SegmentedControl.svelte.d.ts.map +1 -1
  75. package/dist/components/forms/Select.svelte +8 -1
  76. package/dist/components/forms/Select.svelte.d.ts +2 -0
  77. package/dist/components/forms/Select.svelte.d.ts.map +1 -1
  78. package/dist/components/forms/SignaturePad.svelte +436 -0
  79. package/dist/components/forms/SignaturePad.svelte.d.ts +32 -0
  80. package/dist/components/forms/SignaturePad.svelte.d.ts.map +1 -0
  81. package/dist/components/forms/Switch.svelte +8 -1
  82. package/dist/components/forms/Switch.svelte.d.ts +2 -0
  83. package/dist/components/forms/Switch.svelte.d.ts.map +1 -1
  84. package/dist/components/forms/Textarea.svelte +8 -1
  85. package/dist/components/forms/Textarea.svelte.d.ts +2 -0
  86. package/dist/components/forms/Textarea.svelte.d.ts.map +1 -1
  87. package/dist/components/forms/__tests__/CameraCapture.test.js +504 -0
  88. package/dist/components/forms/__tests__/Combobox.test.js +279 -12
  89. package/dist/components/forms/__tests__/Form.test.js +46 -0
  90. package/dist/components/forms/__tests__/Listbox.test.js +303 -0
  91. package/dist/components/forms/__tests__/MultiSelect.test.js +281 -0
  92. package/dist/components/forms/__tests__/SignaturePad.test.js +338 -0
  93. package/dist/components/forms/__tests__/camera-capture-session.test.js +207 -0
  94. package/dist/components/forms/__tests__/capture-form-retry.test.js +166 -0
  95. package/dist/components/forms/__tests__/capture-form.fixture.svelte +38 -0
  96. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts +10 -0
  97. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts.map +1 -0
  98. package/dist/components/forms/__tests__/capture-test-env.d.ts +70 -0
  99. package/dist/components/forms/__tests__/capture-test-env.d.ts.map +1 -0
  100. package/dist/components/forms/__tests__/capture-test-env.js +122 -0
  101. package/dist/components/forms/__tests__/capture-touch.fixture.svelte +15 -0
  102. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts +8 -0
  103. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts.map +1 -0
  104. package/dist/components/forms/__tests__/combobox-form.fixture.svelte +29 -0
  105. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts +14 -0
  106. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts.map +1 -0
  107. package/dist/components/forms/__tests__/form-attachment.fixture.svelte +28 -0
  108. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts +11 -0
  109. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts.map +1 -0
  110. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte +31 -0
  111. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts +13 -0
  112. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts.map +1 -0
  113. package/dist/components/forms/__tests__/form-retry-form.test.js +107 -0
  114. package/dist/components/forms/__tests__/hidden-controls-form-retry.test.js +69 -0
  115. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte +29 -0
  116. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts +9 -0
  117. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts.map +1 -0
  118. package/dist/components/forms/__tests__/listbox-form.fixture.svelte +27 -0
  119. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts +13 -0
  120. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts.map +1 -0
  121. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte +27 -0
  122. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts +13 -0
  123. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts.map +1 -0
  124. package/dist/components/forms/__tests__/native-file-field.test.js +177 -0
  125. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte +28 -0
  126. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts +12 -0
  127. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts.map +1 -0
  128. package/dist/components/forms/__tests__/segmented-posting.test.js +92 -0
  129. package/dist/components/forms/__tests__/signature-pad-logic.test.js +54 -0
  130. package/dist/components/forms/__tests__/touch-density.fixture.svelte +34 -0
  131. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts +8 -0
  132. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts.map +1 -0
  133. package/dist/components/forms/__tests__/touch-density.test.js +66 -0
  134. package/dist/components/forms/camera-capture-session.d.ts +81 -0
  135. package/dist/components/forms/camera-capture-session.d.ts.map +1 -0
  136. package/dist/components/forms/camera-capture-session.js +130 -0
  137. package/dist/components/forms/form-retry/__tests__/boundaries.test.js +23 -0
  138. package/dist/components/forms/form-retry/__tests__/content-digest.test.js +70 -0
  139. package/dist/components/forms/form-retry/__tests__/controller.test.js +802 -0
  140. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts +82 -0
  141. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts.map +1 -0
  142. package/dist/components/forms/form-retry/__tests__/fake-kit.js +180 -0
  143. package/dist/components/forms/form-retry/__tests__/form-draft.test.js +169 -0
  144. package/dist/components/forms/form-retry/__tests__/ssr.test.js +37 -0
  145. package/dist/components/forms/form-retry/__tests__/storage.d.ts +26 -0
  146. package/dist/components/forms/form-retry/__tests__/storage.d.ts.map +1 -0
  147. package/dist/components/forms/form-retry/__tests__/storage.js +63 -0
  148. package/dist/components/forms/form-retry/__tests__/submission-key.test.js +236 -0
  149. package/dist/components/forms/form-retry/__tests__/submitted-draft.test.js +334 -0
  150. package/dist/components/forms/form-retry/content-digest.d.ts +54 -0
  151. package/dist/components/forms/form-retry/content-digest.d.ts.map +1 -0
  152. package/dist/components/forms/form-retry/content-digest.js +169 -0
  153. package/dist/components/forms/form-retry/controller.d.ts +214 -0
  154. package/dist/components/forms/form-retry/controller.d.ts.map +1 -0
  155. package/dist/components/forms/form-retry/controller.js +404 -0
  156. package/dist/components/forms/form-retry/form-draft.d.ts +68 -0
  157. package/dist/components/forms/form-retry/form-draft.d.ts.map +1 -0
  158. package/dist/components/forms/form-retry/form-draft.js +161 -0
  159. package/dist/components/forms/form-retry/index.d.ts +19 -0
  160. package/dist/components/forms/form-retry/index.d.ts.map +1 -0
  161. package/dist/components/forms/form-retry/index.js +16 -0
  162. package/dist/components/forms/form-retry/submission-key.d.ts +244 -0
  163. package/dist/components/forms/form-retry/submission-key.d.ts.map +1 -0
  164. package/dist/components/forms/form-retry/submission-key.js +342 -0
  165. package/dist/components/forms/form-retry/submitted-draft.d.ts +112 -0
  166. package/dist/components/forms/form-retry/submitted-draft.d.ts.map +1 -0
  167. package/dist/components/forms/form-retry/submitted-draft.js +270 -0
  168. package/dist/components/forms/form-retry/types.d.ts +84 -0
  169. package/dist/components/forms/form-retry/types.d.ts.map +1 -0
  170. package/dist/components/forms/form-retry/types.js +13 -0
  171. package/dist/components/forms/index.d.ts +7 -1
  172. package/dist/components/forms/index.d.ts.map +1 -1
  173. package/dist/components/forms/index.js +8 -0
  174. package/dist/components/forms/native-file-field.d.ts +94 -0
  175. package/dist/components/forms/native-file-field.d.ts.map +1 -0
  176. package/dist/components/forms/native-file-field.js +147 -0
  177. package/dist/components/forms/signature-pad-logic.d.ts +50 -0
  178. package/dist/components/forms/signature-pad-logic.d.ts.map +1 -0
  179. package/dist/components/forms/signature-pad-logic.js +48 -0
  180. package/dist/components/forms/types.d.ts +41 -0
  181. package/dist/components/forms/types.d.ts.map +1 -1
  182. package/dist/components/nav/FilterChips.svelte +8 -1
  183. package/dist/components/nav/FilterChips.svelte.d.ts +2 -0
  184. package/dist/components/nav/FilterChips.svelte.d.ts.map +1 -1
  185. package/dist/components/ui/Button.svelte +9 -2
  186. package/dist/components/ui/Button.svelte.d.ts +2 -0
  187. package/dist/components/ui/Button.svelte.d.ts.map +1 -1
  188. package/dist/i18n/strings.ui.d.ts +24 -0
  189. package/dist/i18n/strings.ui.d.ts.map +1 -1
  190. package/dist/i18n/strings.ui.js +26 -0
  191. package/dist/styles/tokens.css +2 -0
  192. package/dist/svelte/__tests__/playground.test.js +2 -1
  193. package/dist/svelte/playground/CapturePreview.svelte +56 -0
  194. package/dist/svelte/playground/CapturePreview.svelte.d.ts +4 -0
  195. package/dist/svelte/playground/CapturePreview.svelte.d.ts.map +1 -0
  196. package/dist/svelte/playground.d.ts.map +1 -1
  197. package/dist/svelte/playground.js +10 -0
  198. package/dist/themes/ThemeProvider.svelte +9 -0
  199. package/dist/themes/ThemeProvider.svelte.d.ts +2 -0
  200. package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
  201. package/dist/themes/css-generator.d.ts.map +1 -1
  202. package/dist/themes/css-generator.js +3 -0
  203. package/dist/themes/styles/glass.css +4 -0
  204. package/dist/themes/styles/happyvertical.css +4 -0
  205. package/dist/themes/styles/material.css +4 -0
  206. package/dist/themes/styles/smrt.css +4 -0
  207. package/dist/themes/styles/studio.css +4 -0
  208. package/package.json +8 -2
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Framework-free helpers that let a capture component (`CameraCapture`,
3
+ * `SignaturePad`) post a `File` it created through a plain
4
+ * `<form method="POST" enctype="multipart/form-data">`, without the caller
5
+ * wiring a hidden input or intercepting submit.
6
+ *
7
+ * Two strategies, chosen once per page by {@link detectNativeFileFieldStrategy}:
8
+ *
9
+ * - `data-transfer` (preferred): the component renders a hidden
10
+ * `<input type="file" name>` and fills it with `new DataTransfer()`, so the
11
+ * browser posts the file exactly like a user-picked one. Needs a constructible
12
+ * `DataTransfer` whose `files` an input accepts (Chrome 60+, Firefox 62+,
13
+ * Safari 14.1+).
14
+ * - `formdata-event` (fallback when `DataTransfer` is unavailable): the hidden
15
+ * input carries no `name`, and a capture-phase `formdata` listener appends the
16
+ * file to the form's entry list while the browser builds it. The browser fires
17
+ * `formdata` for native navigation submits as well as `new FormData(form)`, so
18
+ * the request is the same.
19
+ * - `none`: neither API exists. Nothing is posted for the field; the
20
+ * component's `onCapture` callback remains the only channel. Components expose
21
+ * the active strategy as `data-smrt-file-field` for diagnostics.
22
+ *
23
+ * In both posting strategies an uncommitted field posts what an empty native
24
+ * file input posts (an empty, unnamed `application/octet-stream` file), so the
25
+ * server sees one shape regardless of strategy; inside a disabled `<fieldset>`
26
+ * both post nothing, as a disabled native control does. A reset of the owning form
27
+ * empties the field like a native file input ({@link attachFormResetListener}).
28
+ */
29
+ /** How a capture component posts its file in a native form. */
30
+ export type NativeFileFieldStrategy = 'data-transfer' | 'formdata-event' | 'none';
31
+ /** Globals the strategy probe reads; injectable for tests. */
32
+ export interface NativeFileFieldEnvironment {
33
+ DataTransfer?: unknown;
34
+ FormDataEvent?: unknown;
35
+ File?: unknown;
36
+ /** Creates a detached `<input type="file">` for the assignment probe. */
37
+ createFileInput?: () => HTMLInputElement;
38
+ }
39
+ /**
40
+ * Put `file` into `input` through a fresh `DataTransfer`. Returns `true` only
41
+ * when the input now holds exactly one file. Old engines expose a
42
+ * `DataTransfer` interface whose constructor throws ("Illegal constructor"), or
43
+ * reject the `files` assignment; both report `false`.
44
+ */
45
+ export declare function assignFileWithDataTransfer(input: HTMLInputElement, file: File, DataTransferCtor?: unknown): boolean;
46
+ /**
47
+ * Empty a file input so it posts the native "no file chosen" entry. Scripts may
48
+ * always set a file input's value to the empty string (only non-empty values
49
+ * throw).
50
+ */
51
+ export declare function clearFileInput(input: HTMLInputElement): void;
52
+ /** Pick the strategy for this page. Safe during SSR (returns `none`). */
53
+ export declare function detectNativeFileFieldStrategy(env?: NativeFileFieldEnvironment): NativeFileFieldStrategy;
54
+ /** The entry a `formdata`-event field contributes, read at submit time. */
55
+ export interface FormDataFieldEntry {
56
+ /** The form whose entry list receives the file (the hidden input's `form`). */
57
+ form: HTMLFormElement | null;
58
+ name: string;
59
+ /** The committed file, or `null` to post the native empty-file entry. */
60
+ file: File | null;
61
+ /**
62
+ * The component's hidden input. While it is `:disabled` (inside a disabled
63
+ * `<fieldset>`), nothing is appended — the browser leaves a disabled native
64
+ * control out of the entry list, and this appended entry must match.
65
+ */
66
+ source?: Element | null;
67
+ }
68
+ /** The native "no file chosen" entry: an empty, unnamed octet-stream file. */
69
+ export declare function emptyFileEntry(): File;
70
+ /**
71
+ * Install the `formdata`-event fallback. The listener runs in the capture
72
+ * phase on `target` (normally `document`), so it sees the event whichever form
73
+ * currently owns the field, then appends only for that form. Returns a disposer.
74
+ */
75
+ export declare function attachFormDataFallback(target: Pick<EventTarget, 'addEventListener' | 'removeEventListener'>, readEntry: () => FormDataFieldEntry | null): () => void;
76
+ /**
77
+ * Call `onReset` whenever the form returned by `readForm` resets
78
+ * (`form.reset()`, a reset button, or SvelteKit `enhance`'s `update()` after a
79
+ * success). A native file input is emptied by its form's reset; a capture
80
+ * component must empty its own committed state at the same moment, or it keeps
81
+ * showing a file that no longer posts.
82
+ *
83
+ * Like {@link attachFormDataFallback}, the listener runs in the capture phase
84
+ * on `target` (normally `document`) and reads the owning form at dispatch
85
+ * time, so a field moved between forms needs no re-wiring and a page listener
86
+ * that stops propagation cannot hide the reset. `reset` fires before the
87
+ * browser resets the controls, and `onReset` runs even if a later listener
88
+ * cancels it: the component and the posted entry are then both empty, which
89
+ * keeps what is shown equal to what posts. Returns a disposer.
90
+ */
91
+ export declare function attachFormResetListener(target: Pick<EventTarget, 'addEventListener' | 'removeEventListener'>, readForm: () => HTMLFormElement | null | undefined, onReset: () => void): () => void;
92
+ /** `photo` + `image/png` → `photo.png`; unknown types fall back to `bin`. */
93
+ export declare function fileNameForType(base: string, type: string): string;
94
+ //# sourceMappingURL=native-file-field.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-file-field.d.ts","sourceRoot":"","sources":["../../../src/components/forms/native-file-field.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,+DAA+D;AAC/D,MAAM,MAAM,uBAAuB,GAC/B,eAAe,GACf,gBAAgB,GAChB,MAAM,CAAC;AAEX,8DAA8D;AAC9D,MAAM,WAAW,0BAA0B;IACzC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,gBAAgB,CAAC;CAC1C;AAqBD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,IAAI,EACV,gBAAgB,GAAE,OACH,GACd,OAAO,CAUT;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAE5D;AAED,yEAAyE;AACzE,wBAAgB,6BAA6B,CAC3C,GAAG,GAAE,0BAAiD,GACrD,uBAAuB,CAoBzB;AAED,2EAA2E;AAC3E,MAAM,WAAW,kBAAkB;IACjC,+EAA+E;IAC/E,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CACzB;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,kBAAkB,GAAG,qBAAqB,CAAC,EACrE,SAAS,EAAE,MAAM,kBAAkB,GAAG,IAAI,GACzC,MAAM,IAAI,CAYZ;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,kBAAkB,GAAG,qBAAqB,CAAC,EACrE,QAAQ,EAAE,MAAM,eAAe,GAAG,IAAI,GAAG,SAAS,EAClD,OAAO,EAAE,MAAM,IAAI,GAClB,MAAM,IAAI,CAOZ;AAQD,6EAA6E;AAC7E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAElE"}
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Framework-free helpers that let a capture component (`CameraCapture`,
3
+ * `SignaturePad`) post a `File` it created through a plain
4
+ * `<form method="POST" enctype="multipart/form-data">`, without the caller
5
+ * wiring a hidden input or intercepting submit.
6
+ *
7
+ * Two strategies, chosen once per page by {@link detectNativeFileFieldStrategy}:
8
+ *
9
+ * - `data-transfer` (preferred): the component renders a hidden
10
+ * `<input type="file" name>` and fills it with `new DataTransfer()`, so the
11
+ * browser posts the file exactly like a user-picked one. Needs a constructible
12
+ * `DataTransfer` whose `files` an input accepts (Chrome 60+, Firefox 62+,
13
+ * Safari 14.1+).
14
+ * - `formdata-event` (fallback when `DataTransfer` is unavailable): the hidden
15
+ * input carries no `name`, and a capture-phase `formdata` listener appends the
16
+ * file to the form's entry list while the browser builds it. The browser fires
17
+ * `formdata` for native navigation submits as well as `new FormData(form)`, so
18
+ * the request is the same.
19
+ * - `none`: neither API exists. Nothing is posted for the field; the
20
+ * component's `onCapture` callback remains the only channel. Components expose
21
+ * the active strategy as `data-smrt-file-field` for diagnostics.
22
+ *
23
+ * In both posting strategies an uncommitted field posts what an empty native
24
+ * file input posts (an empty, unnamed `application/octet-stream` file), so the
25
+ * server sees one shape regardless of strategy; inside a disabled `<fieldset>`
26
+ * both post nothing, as a disabled native control does. A reset of the owning form
27
+ * empties the field like a native file input ({@link attachFormResetListener}).
28
+ */
29
+ function browserEnvironment() {
30
+ const scope = globalThis;
31
+ return {
32
+ DataTransfer: scope.DataTransfer,
33
+ FormDataEvent: scope.FormDataEvent,
34
+ File: scope.File,
35
+ createFileInput: typeof document === 'undefined'
36
+ ? undefined
37
+ : () => {
38
+ const input = document.createElement('input');
39
+ input.type = 'file';
40
+ return input;
41
+ },
42
+ };
43
+ }
44
+ /**
45
+ * Put `file` into `input` through a fresh `DataTransfer`. Returns `true` only
46
+ * when the input now holds exactly one file. Old engines expose a
47
+ * `DataTransfer` interface whose constructor throws ("Illegal constructor"), or
48
+ * reject the `files` assignment; both report `false`.
49
+ */
50
+ export function assignFileWithDataTransfer(input, file, DataTransferCtor = globalThis
51
+ .DataTransfer) {
52
+ if (typeof DataTransferCtor !== 'function')
53
+ return false;
54
+ try {
55
+ const transfer = new DataTransferCtor();
56
+ transfer.items.add(file);
57
+ input.files = transfer.files;
58
+ return input.files?.length === 1;
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
64
+ /**
65
+ * Empty a file input so it posts the native "no file chosen" entry. Scripts may
66
+ * always set a file input's value to the empty string (only non-empty values
67
+ * throw).
68
+ */
69
+ export function clearFileInput(input) {
70
+ input.value = '';
71
+ }
72
+ /** Pick the strategy for this page. Safe during SSR (returns `none`). */
73
+ export function detectNativeFileFieldStrategy(env = browserEnvironment()) {
74
+ const FileCtor = env.File;
75
+ if (env.createFileInput && typeof FileCtor === 'function') {
76
+ try {
77
+ const probe = new FileCtor([], 'probe.bin');
78
+ if (assignFileWithDataTransfer(env.createFileInput(), probe, env.DataTransfer)) {
79
+ return 'data-transfer';
80
+ }
81
+ }
82
+ catch {
83
+ // Fall through to the next strategy.
84
+ }
85
+ }
86
+ if (typeof env.FormDataEvent === 'function')
87
+ return 'formdata-event';
88
+ return 'none';
89
+ }
90
+ /** The native "no file chosen" entry: an empty, unnamed octet-stream file. */
91
+ export function emptyFileEntry() {
92
+ return new File([], '', { type: 'application/octet-stream' });
93
+ }
94
+ /**
95
+ * Install the `formdata`-event fallback. The listener runs in the capture
96
+ * phase on `target` (normally `document`), so it sees the event whichever form
97
+ * currently owns the field, then appends only for that form. Returns a disposer.
98
+ */
99
+ export function attachFormDataFallback(target, readEntry) {
100
+ const listener = (event) => {
101
+ const entry = readEntry();
102
+ if (!entry?.form || !entry.name || event.target !== entry.form)
103
+ return;
104
+ if (entry.source?.matches(':disabled'))
105
+ return;
106
+ const formData = event.formData;
107
+ if (!formData)
108
+ return;
109
+ const file = entry.file ?? emptyFileEntry();
110
+ formData.append(entry.name, file, file.name);
111
+ };
112
+ target.addEventListener('formdata', listener, true);
113
+ return () => target.removeEventListener('formdata', listener, true);
114
+ }
115
+ /**
116
+ * Call `onReset` whenever the form returned by `readForm` resets
117
+ * (`form.reset()`, a reset button, or SvelteKit `enhance`'s `update()` after a
118
+ * success). A native file input is emptied by its form's reset; a capture
119
+ * component must empty its own committed state at the same moment, or it keeps
120
+ * showing a file that no longer posts.
121
+ *
122
+ * Like {@link attachFormDataFallback}, the listener runs in the capture phase
123
+ * on `target` (normally `document`) and reads the owning form at dispatch
124
+ * time, so a field moved between forms needs no re-wiring and a page listener
125
+ * that stops propagation cannot hide the reset. `reset` fires before the
126
+ * browser resets the controls, and `onReset` runs even if a later listener
127
+ * cancels it: the component and the posted entry are then both empty, which
128
+ * keeps what is shown equal to what posts. Returns a disposer.
129
+ */
130
+ export function attachFormResetListener(target, readForm, onReset) {
131
+ const listener = (event) => {
132
+ const form = readForm();
133
+ if (form && event.target === form)
134
+ onReset();
135
+ };
136
+ target.addEventListener('reset', listener, true);
137
+ return () => target.removeEventListener('reset', listener, true);
138
+ }
139
+ const EXTENSIONS = {
140
+ 'image/jpeg': 'jpg',
141
+ 'image/png': 'png',
142
+ 'image/webp': 'webp',
143
+ };
144
+ /** `photo` + `image/png` → `photo.png`; unknown types fall back to `bin`. */
145
+ export function fileNameForType(base, type) {
146
+ return `${base}.${EXTENSIONS[type] ?? 'bin'}`;
147
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Pure input logic for `SignaturePad.svelte`: the pointer-type gate and the
3
+ * pointer-to-canvas coordinate mapping. Free of DOM and settings dependencies
4
+ * so both are testable without a browser. The component takes `stylusOnly` as a
5
+ * prop the caller resolves (from a tenant setting, a device profile, …) and
6
+ * never decides it itself.
7
+ *
8
+ * Ported from teamworks-os `signature-pad-logic.ts`, smrt#3290.
9
+ */
10
+ /** `PointerEvent.pointerType`; browsers may report values beyond the three known ones. */
11
+ export type SignaturePointerType = 'pen' | 'touch' | 'mouse' | (string & {});
12
+ /**
13
+ * Paper and ink colours for the signature bitmap. A signature is document
14
+ * content, not chrome: it stays dark ink on white paper in every theme and
15
+ * colour scheme, so the exported PNG reads the same wherever it is shown.
16
+ */
17
+ export declare const SIGNATURE_PAPER_COLOR = "#ffffff";
18
+ export declare const SIGNATURE_INK_COLOR = "#111111";
19
+ /**
20
+ * Whether a pointer of `pointerType` may draw a stroke.
21
+ *
22
+ * With `stylusOnly`, only `pen` is accepted: finger and mouse input are
23
+ * ignored outright, since nothing at the pointer-event level can tell a
24
+ * gloved finger from a stylus. Otherwise pen, touch and mouse are accepted.
25
+ * Unknown pointer types are rejected in both modes.
26
+ */
27
+ export declare function isAcceptedPointerType(pointerType: SignaturePointerType, stylusOnly: boolean): boolean;
28
+ /** The subset of `DOMRect` the coordinate mapping needs. */
29
+ export interface CanvasDisplayRect {
30
+ left: number;
31
+ top: number;
32
+ width: number;
33
+ height: number;
34
+ }
35
+ /**
36
+ * Maps viewport `clientX`/`clientY` (CSS pixels) to the canvas's backing-pixel
37
+ * space.
38
+ *
39
+ * The canvas has a fixed backing resolution but is displayed at `width: 100%`,
40
+ * so the raw `clientX - rect.left` offset is only right when the displayed size
41
+ * happens to equal the backing size; anywhere else the ink drifts from the
42
+ * stylus and clips near the edges. Scaling by `backing / displayed` keeps them
43
+ * aligned. A zero-size rect (not laid out yet) falls back to 1:1 instead of
44
+ * dividing by zero.
45
+ */
46
+ export declare function mapPointerToCanvasPoint(clientX: number, clientY: number, rect: CanvasDisplayRect, canvasWidth: number, canvasHeight: number): {
47
+ x: number;
48
+ y: number;
49
+ };
50
+ //# sourceMappingURL=signature-pad-logic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signature-pad-logic.d.ts","sourceRoot":"","sources":["../../../src/components/forms/signature-pad-logic.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,0FAA0F;AAC1F,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,OAAO,GAAG,OAAO,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAE7E;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,YAAY,CAAC;AAC/C,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAE7C;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,oBAAoB,EACjC,UAAU,EAAE,OAAO,GAClB,OAAO,CAKT;AAED,4DAA4D;AAC5D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,iBAAiB,EACvB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAO1B"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Pure input logic for `SignaturePad.svelte`: the pointer-type gate and the
3
+ * pointer-to-canvas coordinate mapping. Free of DOM and settings dependencies
4
+ * so both are testable without a browser. The component takes `stylusOnly` as a
5
+ * prop the caller resolves (from a tenant setting, a device profile, …) and
6
+ * never decides it itself.
7
+ *
8
+ * Ported from teamworks-os `signature-pad-logic.ts`, smrt#3290.
9
+ */
10
+ /**
11
+ * Paper and ink colours for the signature bitmap. A signature is document
12
+ * content, not chrome: it stays dark ink on white paper in every theme and
13
+ * colour scheme, so the exported PNG reads the same wherever it is shown.
14
+ */
15
+ export const SIGNATURE_PAPER_COLOR = '#ffffff';
16
+ export const SIGNATURE_INK_COLOR = '#111111';
17
+ /**
18
+ * Whether a pointer of `pointerType` may draw a stroke.
19
+ *
20
+ * With `stylusOnly`, only `pen` is accepted: finger and mouse input are
21
+ * ignored outright, since nothing at the pointer-event level can tell a
22
+ * gloved finger from a stylus. Otherwise pen, touch and mouse are accepted.
23
+ * Unknown pointer types are rejected in both modes.
24
+ */
25
+ export function isAcceptedPointerType(pointerType, stylusOnly) {
26
+ if (stylusOnly)
27
+ return pointerType === 'pen';
28
+ return (pointerType === 'pen' || pointerType === 'touch' || pointerType === 'mouse');
29
+ }
30
+ /**
31
+ * Maps viewport `clientX`/`clientY` (CSS pixels) to the canvas's backing-pixel
32
+ * space.
33
+ *
34
+ * The canvas has a fixed backing resolution but is displayed at `width: 100%`,
35
+ * so the raw `clientX - rect.left` offset is only right when the displayed size
36
+ * happens to equal the backing size; anywhere else the ink drifts from the
37
+ * stylus and clips near the edges. Scaling by `backing / displayed` keeps them
38
+ * aligned. A zero-size rect (not laid out yet) falls back to 1:1 instead of
39
+ * dividing by zero.
40
+ */
41
+ export function mapPointerToCanvasPoint(clientX, clientY, rect, canvasWidth, canvasHeight) {
42
+ const scaleX = rect.width > 0 ? canvasWidth / rect.width : 1;
43
+ const scaleY = rect.height > 0 ? canvasHeight / rect.height : 1;
44
+ return {
45
+ x: (clientX - rect.left) * scaleX,
46
+ y: (clientY - rect.top) * scaleY,
47
+ };
48
+ }
@@ -11,4 +11,45 @@ export interface RangeSliderValue {
11
11
  export interface SegmentedControlOption extends ControlOption {
12
12
  icon?: string;
13
13
  }
14
+ /** What `CameraCapture` and `SignaturePad` hand to `onCapture`. */
15
+ export interface CaptureResult {
16
+ /** The encoded image (JPEG/PNG/WebP for photos, PNG for signatures). */
17
+ blob: Blob;
18
+ /** The same image as a `data:` URL, convenient for an inline preview. */
19
+ dataUrl: string;
20
+ }
21
+ /** Rendered states of `CameraCapture`, exposed as its `data-state` attribute. */
22
+ export type CameraCaptureState = 'off' | 'starting' | 'streaming' | 'reviewing' | 'committed' | 'permission-denied' | 'no-camera' | 'unsupported' | 'error' | 'fallback';
23
+ /** Text overrides for `CameraCapture`; defaults come from the `ui` i18n catalog. */
24
+ export interface CameraCaptureLabels {
25
+ region: string;
26
+ preview: string;
27
+ starting: string;
28
+ off: string;
29
+ unsupported: string;
30
+ permissionDenied: string;
31
+ noCamera: string;
32
+ error: string;
33
+ retry: string;
34
+ capture: string;
35
+ retake: string;
36
+ usePhoto: string;
37
+ reviewAlt: string;
38
+ committedAlt: string;
39
+ committed: string;
40
+ choosePhoto: string;
41
+ }
42
+ /** Rendered states of `SignaturePad`, exposed as its `data-state` attribute. */
43
+ export type SignaturePadState = 'empty' | 'signed' | 'committed';
44
+ /** Text overrides for `SignaturePad`; defaults come from the `ui` i18n catalog. */
45
+ export interface SignaturePadLabels {
46
+ region: string;
47
+ canvasEmpty: string;
48
+ canvasSigned: string;
49
+ hintAny: string;
50
+ hintStylus: string;
51
+ clear: string;
52
+ useSignature: string;
53
+ committed: string;
54
+ }
14
55
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/forms/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,sBAAuB,SAAQ,aAAa;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/forms/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,sBAAuB,SAAQ,aAAa;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,mEAAmE;AACnE,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,IAAI,EAAE,IAAI,CAAC;IACX,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,iFAAiF;AACjF,MAAM,MAAM,kBAAkB,GAC1B,KAAK,GACL,UAAU,GACV,WAAW,GACX,WAAW,GACX,WAAW,GACX,mBAAmB,GACnB,WAAW,GACX,aAAa,GACb,OAAO,GACP,UAAU,CAAC;AAEf,oFAAoF;AACpF,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,gFAAgF;AAChF,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEjE,mFAAmF;AACnF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB"}
@@ -11,6 +11,8 @@ import { Icon } from '../display/index.js';
11
11
  import type { FilterOption } from './types.js';
12
12
 
13
13
  export interface Props {
14
+ /** Target density; omit to inherit the ThemeProvider setting. */
15
+ density?: 'comfortable' | 'touch';
14
16
  /** Available filter options */
15
17
  options: FilterOption[];
16
18
  /** Currently selected value */
@@ -32,6 +34,7 @@ const {
32
34
  size = 'md',
33
35
  showAll = false,
34
36
  allLabel = 'All',
37
+ density,
35
38
  }: Props = $props();
36
39
 
37
40
  // Build full options list with optional "all"
@@ -54,7 +57,7 @@ function handleClick(value: string) {
54
57
  }
55
58
  </script>
56
59
 
57
- <div class="filter-chips" class:sm={size === 'sm'} role="radiogroup">
60
+ <div data-density={density} class="filter-chips" class:sm={size === 'sm'} role="radiogroup">
58
61
  {#each allOptions as option (option.value)}
59
62
  {@const isActive = option.value === selected}
60
63
  <button
@@ -149,4 +152,8 @@ function handleClick(value: string) {
149
152
  .active .chip-count {
150
153
  color: var(--smrt-color-on-secondary-container);
151
154
  }
155
+
156
+ .filter-chips[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
157
+ .filter-chips[data-density='comfortable'] { --smrt-control-target-min: 0px; }
158
+ .filter-chip { min-height: var(--smrt-control-target-min, 0px); min-width: var(--smrt-control-target-min, 0px); }
152
159
  </style>
@@ -1,5 +1,7 @@
1
1
  import type { FilterOption } from './types.js';
2
2
  export interface Props {
3
+ /** Target density; omit to inherit the ThemeProvider setting. */
4
+ density?: 'comfortable' | 'touch';
3
5
  /** Available filter options */
4
6
  options: FilterOption[];
5
7
  /** Currently selected value */
@@ -1 +1 @@
1
- {"version":3,"file":"FilterChips.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/nav/FilterChips.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,MAAM,WAAW,KAAK;IACpB,+BAA+B;IAC/B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,mBAAmB;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,wBAAwB;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uBAAuB;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAyDD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"FilterChips.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/nav/FilterChips.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,MAAM,WAAW,KAAK;IACpB,iEAAiE;IACjE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAClC,+BAA+B;IAC/B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,mBAAmB;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,wBAAwB;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uBAAuB;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA0DD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -14,6 +14,8 @@ import type { ButtonSize, ButtonVariant } from '../../types-generic';
14
14
 
15
15
  /** Props for Button component */
16
16
  export interface Props extends Omit<HTMLButtonAttributes, 'class' | 'href'> {
17
+ /** Target density; omit to inherit the ThemeProvider setting. */
18
+ density?: 'comfortable' | 'touch';
17
19
  /** Visual variant */
18
20
  variant?: ButtonVariant;
19
21
  /** Size variant */
@@ -62,6 +64,7 @@ const {
62
64
  target,
63
65
  rel,
64
66
  download,
67
+ density,
65
68
  ...rest
66
69
  }: Props = $props();
67
70
 
@@ -118,7 +121,7 @@ function handleClick(event: MouseEvent) {
118
121
  {target}
119
122
  {rel}
120
123
  {download}
121
- class="button {variant} {size} {className}"
124
+ data-density={density} class="button {variant} {size} {className}"
122
125
  class:disabled={isDisabled}
123
126
  class:full-width={fullWidth}
124
127
  class:loading
@@ -140,7 +143,7 @@ function handleClick(event: MouseEvent) {
140
143
  {type}
141
144
  disabled={isDisabled}
142
145
  aria-busy={loading}
143
- class="button {variant} {size} {className}"
146
+ data-density={density} class="button {variant} {size} {className}"
144
147
  class:full-width={fullWidth}
145
148
  class:loading
146
149
  onclick={handleClick}
@@ -308,4 +311,8 @@ function handleClick(event: MouseEvent) {
308
311
  animation: none;
309
312
  }
310
313
  }
314
+
315
+ .button[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
316
+ .button[data-density='comfortable'] { --smrt-control-target-min: 0px; }
317
+ .button { min-height: var(--smrt-control-target-min, 0px); min-width: var(--smrt-control-target-min, 0px); }
311
318
  </style>
@@ -9,6 +9,8 @@ import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements
9
9
  import type { ButtonSize, ButtonVariant } from '../../types-generic';
10
10
  /** Props for Button component */
11
11
  export interface Props extends Omit<HTMLButtonAttributes, 'class' | 'href'> {
12
+ /** Target density; omit to inherit the ThemeProvider setting. */
13
+ density?: 'comfortable' | 'touch';
12
14
  /** Visual variant */
13
15
  variant?: ButtonVariant;
14
16
  /** Size variant */
@@ -1 +1 @@
1
- {"version":3,"file":"Button.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/ui/Button.svelte.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EACV,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGrE,iCAAiC;AACjC,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,MAAM,CAAC;IACzE,qBAAqB;IACrB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,mBAAmB;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wBAAwB;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACxC,6EAA6E;IAC7E,GAAG,CAAC,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAClC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,oBAAoB,CAAC,UAAU,CAAC,CAAC;CAC7C;AAmGD,QAAA,MAAM,MAAM,2CAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"Button.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/ui/Button.svelte.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EACV,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGrE,iCAAiC;AACjC,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,MAAM,CAAC;IACzE,iEAAiE;IACjE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAClC,qBAAqB;IACrB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,mBAAmB;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wBAAwB;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACxC,6EAA6E;IAC7E,GAAG,CAAC,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAClC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,oBAAoB,CAAC,UAAU,CAAC,CAAC;CAC7C;AAoGD,QAAA,MAAM,MAAM,2CAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
@@ -73,6 +73,30 @@ export declare const M: {
73
73
  readonly 'ui.pagination.last_page': "ui.pagination.last_page";
74
74
  readonly 'ui.reaction_picker.label': "ui.reaction_picker.label";
75
75
  readonly 'ui.reaction_picker.react_with': "ui.reaction_picker.react_with";
76
+ readonly 'ui.camera_capture.region': "ui.camera_capture.region";
77
+ readonly 'ui.camera_capture.preview': "ui.camera_capture.preview";
78
+ readonly 'ui.camera_capture.starting': "ui.camera_capture.starting";
79
+ readonly 'ui.camera_capture.off': "ui.camera_capture.off";
80
+ readonly 'ui.camera_capture.unsupported': "ui.camera_capture.unsupported";
81
+ readonly 'ui.camera_capture.permission_denied': "ui.camera_capture.permission_denied";
82
+ readonly 'ui.camera_capture.no_camera': "ui.camera_capture.no_camera";
83
+ readonly 'ui.camera_capture.error': "ui.camera_capture.error";
84
+ readonly 'ui.camera_capture.retry': "ui.camera_capture.retry";
85
+ readonly 'ui.camera_capture.capture': "ui.camera_capture.capture";
86
+ readonly 'ui.camera_capture.retake': "ui.camera_capture.retake";
87
+ readonly 'ui.camera_capture.use_photo': "ui.camera_capture.use_photo";
88
+ readonly 'ui.camera_capture.review_alt': "ui.camera_capture.review_alt";
89
+ readonly 'ui.camera_capture.committed_alt': "ui.camera_capture.committed_alt";
90
+ readonly 'ui.camera_capture.committed': "ui.camera_capture.committed";
91
+ readonly 'ui.camera_capture.choose_photo': "ui.camera_capture.choose_photo";
92
+ readonly 'ui.signature_pad.region': "ui.signature_pad.region";
93
+ readonly 'ui.signature_pad.canvas_empty': "ui.signature_pad.canvas_empty";
94
+ readonly 'ui.signature_pad.canvas_signed': "ui.signature_pad.canvas_signed";
95
+ readonly 'ui.signature_pad.hint_any': "ui.signature_pad.hint_any";
96
+ readonly 'ui.signature_pad.hint_stylus': "ui.signature_pad.hint_stylus";
97
+ readonly 'ui.signature_pad.clear': "ui.signature_pad.clear";
98
+ readonly 'ui.signature_pad.use_signature': "ui.signature_pad.use_signature";
99
+ readonly 'ui.signature_pad.committed': "ui.signature_pad.committed";
76
100
  readonly 'ui.staged_control_review.region': "ui.staged_control_review.region";
77
101
  readonly 'ui.staged_control_review.heading': "ui.staged_control_review.heading";
78
102
  readonly 'ui.staged_control_review.description': "ui.staged_control_review.description";
@@ -1 +1 @@
1
- {"version":3,"file":"strings.ui.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ui.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkIZ,CAAC"}
1
+ {"version":3,"file":"strings.ui.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ui.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgKZ,CAAC"}
@@ -90,6 +90,32 @@ export const M = defineMessages({
90
90
  // chat/ReactionPicker.svelte
91
91
  'ui.reaction_picker.label': 'Add reaction',
92
92
  'ui.reaction_picker.react_with': 'React with {emoji}',
93
+ // forms/CameraCapture.svelte
94
+ 'ui.camera_capture.region': 'Camera',
95
+ 'ui.camera_capture.preview': 'Live camera preview',
96
+ 'ui.camera_capture.starting': 'Starting camera…',
97
+ 'ui.camera_capture.off': 'The camera is off.',
98
+ 'ui.camera_capture.unsupported': 'This browser cannot use the camera. Open this page over HTTPS in a current browser.',
99
+ 'ui.camera_capture.permission_denied': 'Camera access was denied. Allow camera access for this site, then try again.',
100
+ 'ui.camera_capture.no_camera': 'No camera was found on this device.',
101
+ 'ui.camera_capture.error': 'Could not access the camera.',
102
+ 'ui.camera_capture.retry': 'Try again',
103
+ 'ui.camera_capture.capture': 'Take photo',
104
+ 'ui.camera_capture.retake': 'Retake',
105
+ 'ui.camera_capture.use_photo': 'Use photo',
106
+ 'ui.camera_capture.review_alt': 'Captured photo, not yet used',
107
+ 'ui.camera_capture.committed_alt': 'Attached photo',
108
+ 'ui.camera_capture.committed': 'Photo attached.',
109
+ 'ui.camera_capture.choose_photo': 'Take or choose a photo',
110
+ // forms/SignaturePad.svelte
111
+ 'ui.signature_pad.region': 'Signature',
112
+ 'ui.signature_pad.canvas_empty': 'Signature area, not yet signed',
113
+ 'ui.signature_pad.canvas_signed': 'Signature area, signed',
114
+ 'ui.signature_pad.hint_any': 'Sign with a stylus, your finger, or the mouse.',
115
+ 'ui.signature_pad.hint_stylus': 'Sign with the stylus.',
116
+ 'ui.signature_pad.clear': 'Clear',
117
+ 'ui.signature_pad.use_signature': 'Use signature',
118
+ 'ui.signature_pad.committed': 'Signature attached.',
93
119
  // forms/StagedControlReview.svelte
94
120
  'ui.staged_control_review.region': 'Review proposed changes',
95
121
  'ui.staged_control_review.heading': 'Proposed changes',
@@ -4,6 +4,8 @@
4
4
  */
5
5
 
6
6
  :root {
7
+ --smrt-touch-target-min: 48px;
8
+ --smrt-control-target-min: 0px;
7
9
  /* Colors - Primary */
8
10
  --color-primary-main: #1976d2;
9
11
  --color-primary-light: #e3f2fd;
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import playground from '../playground.js';
3
3
  describe('smrt-ui playground', () => {
4
- it('publishes themed base-control and data-table previews', () => {
4
+ it('publishes themed base-control, data-table, and capture previews', () => {
5
5
  expect(playground.packageName).toBe('@happyvertical/smrt-ui');
6
6
  expect(playground.entries.map((entry) => entry.id)).toEqual([
7
7
  'base-controls',
@@ -10,6 +10,7 @@ describe('smrt-ui playground', () => {
10
10
  'collections',
11
11
  'data-table',
12
12
  'calendar',
13
+ 'capture',
13
14
  ]);
14
15
  for (const entry of playground.entries) {
15
16
  expect(entry.loadComponent).toEqual(expect.any(Function));