@happyvertical/smrt-ui 0.52.0 → 0.53.1

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
@@ -13,6 +13,8 @@ import { tryGetFormGroupContext } from './form-group-context.js';
13
13
  import { useControlRegistration } from './use-control-registration.svelte.js';
14
14
 
15
15
  export interface Props extends Omit<HTMLSelectAttributes, 'class' | 'value'> {
16
+ /** Target density; omit to inherit the ThemeProvider setting. */
17
+ density?: 'comfortable' | 'touch';
16
18
  /** The currently selected option value. */
17
19
  value?: string;
18
20
  /** Additional CSS class names. */
@@ -35,6 +37,7 @@ let {
35
37
  'aria-label': ariaLabel,
36
38
  'aria-describedby': ariaDescribedby,
37
39
  'aria-invalid': ariaInvalid,
40
+ density,
38
41
  ...rest
39
42
  }: Props = $props();
40
43
 
@@ -140,7 +143,7 @@ export function getElement(): HTMLSelectElement | null {
140
143
  aria-label={ariaLabel}
141
144
  aria-describedby={resolvedDescribedBy}
142
145
  aria-invalid={resolvedInvalid}
143
- class="select {className}"
146
+ data-density={density} class="select {className}"
144
147
  data-smrt-control={resolvedControlId}
145
148
  data-smrt-form={controlInteraction?.formId}
146
149
  data-smrt-subject-type={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.type}
@@ -204,4 +207,8 @@ export function getElement(): HTMLSelectElement | null {
204
207
  transition: none;
205
208
  }
206
209
  }
210
+
211
+ .select[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
212
+ .select[data-density='comfortable'] { --smrt-control-target-min: 0px; }
213
+ .select { min-height: var(--smrt-control-target-min, 0px); }
207
214
  </style>
@@ -2,6 +2,8 @@ import type { Snippet } from 'svelte';
2
2
  import type { HTMLSelectAttributes } from 'svelte/elements';
3
3
  import type { ControlInteractionOptions } from './control-interaction.js';
4
4
  export interface Props extends Omit<HTMLSelectAttributes, 'class' | 'value'> {
5
+ /** Target density; omit to inherit the ThemeProvider setting. */
6
+ density?: 'comfortable' | 'touch';
5
7
  /** The currently selected option value. */
6
8
  value?: string;
7
9
  /** Additional CSS class names. */
@@ -1 +1 @@
1
- {"version":3,"file":"Select.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Select.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAM5D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAO1E,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,OAAO,CAAC;IAC1E,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD,wCAAwC;IACxC,QAAQ,EAAE,OAAO,CAAC;CACnB;AA+HD,QAAA,MAAM,MAAM;iBAjBO,IAAI;sBAIC,iBAAiB,GAAG,IAAI;WAaI,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"Select.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Select.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAM5D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAO1E,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,OAAO,CAAC;IAC1E,iEAAiE;IACjE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAClC,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD,wCAAwC;IACxC,QAAQ,EAAE,OAAO,CAAC;CACnB;AAgID,QAAA,MAAM,MAAM;iBAjBO,IAAI;sBAIC,iBAAiB,GAAG,IAAI;WAaI,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
@@ -0,0 +1,436 @@
1
+ <!--
2
+ Signature pad that draws on its own canvas and returns a PNG.
3
+
4
+ A committed signature leaves through `onCapture({ blob, dataUrl })` and, when
5
+ `name` is set, posts in a plain native form as a file field, the same way
6
+ `CameraCapture` posts its photo (`native-file-field.ts`).
7
+
8
+ `stylusOnly` is resolved by the caller (a tenant setting, a device profile)
9
+ and passed in; the pointer-type gate is the pure `isAcceptedPointerType()` in
10
+ `signature-pad-logic.ts`. "Use signature" stays disabled until an accepted
11
+ stroke exists, so an empty canvas can never be submitted. Once committed the
12
+ canvas is locked; "Clear" discards the signature and unlocks it. A reset of
13
+ the owning form does the same, as it empties a native file input.
14
+
15
+ The bitmap is painted white and inked dark in every theme and colour scheme:
16
+ a signature is document content, so the exported PNG reads the same wherever
17
+ it is shown, and the pad looks like paper in dark mode too.
18
+
19
+ Ported from teamworks-os `SignaturePad.svelte`, smrt#3290.
20
+ -->
21
+ <script lang="ts">
22
+ import { onMount, untrack } from 'svelte';
23
+ import { M, useI18n } from '../../i18n/index.js';
24
+ import {
25
+ focusControl,
26
+ highlightControl,
27
+ revealControl,
28
+ } from './control-dom.js';
29
+ import type { ControlInteractionOptions } from './control-interaction.js';
30
+ import { tryGetControlInteractionContext } from './control-interaction-context.js';
31
+ import {
32
+ assignFileWithDataTransfer,
33
+ attachFormDataFallback,
34
+ attachFormResetListener,
35
+ clearFileInput,
36
+ detectNativeFileFieldStrategy,
37
+ type NativeFileFieldStrategy,
38
+ } from './native-file-field.js';
39
+ import {
40
+ isAcceptedPointerType,
41
+ mapPointerToCanvasPoint,
42
+ SIGNATURE_INK_COLOR,
43
+ SIGNATURE_PAPER_COLOR,
44
+ } from './signature-pad-logic.js';
45
+ import type {
46
+ CaptureResult,
47
+ SignaturePadLabels,
48
+ SignaturePadState,
49
+ } from './types.js';
50
+ import { useControlRegistration } from './use-control-registration.svelte.js';
51
+
52
+ export interface Props {
53
+ /** Accept pen input only; the caller resolves this rule. */
54
+ stylusOnly?: boolean;
55
+ /** Ignores input and disables both actions. */
56
+ disabled?: boolean;
57
+ /** Called when the user commits a signature with "Use signature". */
58
+ onCapture?: (result: CaptureResult) => void;
59
+ /** Called when a committed signature is discarded by "Clear" or a form reset. */
60
+ onClear?: () => void;
61
+ /** Field name: the committed PNG posts as a file in a native form. */
62
+ name?: string;
63
+ /** Posted file name. */
64
+ fileName?: string;
65
+ /** Canvas backing width in pixels (the display width is responsive). */
66
+ width?: number;
67
+ /** Canvas backing height in pixels. */
68
+ height?: number;
69
+ /** Text overrides; defaults come from the `ui` i18n catalog. */
70
+ labels?: Partial<SignaturePadLabels>;
71
+ /** DOM id of the component root. */
72
+ id?: string;
73
+ /** CSS class to apply to the component root. */
74
+ class?: string;
75
+ /** Interaction options or false to disable registration. */
76
+ interaction?: ControlInteractionOptions | false;
77
+ }
78
+
79
+ let {
80
+ stylusOnly = false,
81
+ disabled = false,
82
+ onCapture,
83
+ onClear,
84
+ name,
85
+ fileName = 'signature.png',
86
+ width = 600,
87
+ height = 240,
88
+ labels,
89
+ id,
90
+ class: className = '',
91
+ interaction,
92
+ }: Props = $props();
93
+
94
+ const { t } = useI18n();
95
+ const text = $derived<SignaturePadLabels>({
96
+ region: t(M['ui.signature_pad.region']),
97
+ canvasEmpty: t(M['ui.signature_pad.canvas_empty']),
98
+ canvasSigned: t(M['ui.signature_pad.canvas_signed']),
99
+ hintAny: t(M['ui.signature_pad.hint_any']),
100
+ hintStylus: t(M['ui.signature_pad.hint_stylus']),
101
+ clear: t(M['ui.signature_pad.clear']),
102
+ useSignature: t(M['ui.signature_pad.use_signature']),
103
+ committed: t(M['ui.signature_pad.committed']),
104
+ ...labels,
105
+ });
106
+
107
+ const instanceId = $props.id();
108
+ const interactionContext = tryGetControlInteractionContext();
109
+ const resolvedId = $derived(id ?? `smrt-signature-${instanceId}`);
110
+ const hintId = $derived(`${resolvedId}-hint`);
111
+ const controlId = $derived(
112
+ interaction === false ? undefined : (interaction?.id ?? name ?? resolvedId),
113
+ );
114
+
115
+ let padState = $state<SignaturePadState>('empty');
116
+ let strategy = $state<NativeFileFieldStrategy>('data-transfer');
117
+ let rootEl = $state<HTMLDivElement | null>(null);
118
+ let canvasEl = $state<HTMLCanvasElement | null>(null);
119
+ let fieldEl = $state<HTMLInputElement | null>(null);
120
+ let activePointerId: number | null = null;
121
+ let lastPoint: { x: number; y: number } | null = null;
122
+ let committedFile: File | null = null;
123
+ // Set while "Use signature" encodes the PNG: the pad is locked so the posted
124
+ // file always equals the ink on screen, and a second click cannot commit twice.
125
+ let encoding = $state(false);
126
+ let encodeRequest = 0;
127
+
128
+ function paintPaper(): void {
129
+ const context = canvasEl?.getContext('2d');
130
+ if (!context || !canvasEl) return;
131
+ context.fillStyle = SIGNATURE_PAPER_COLOR;
132
+ context.fillRect(0, 0, canvasEl.width, canvasEl.height);
133
+ }
134
+
135
+ onMount(() => {
136
+ strategy = detectNativeFileFieldStrategy();
137
+ });
138
+
139
+ // Paint paper when the canvas mounts. A backing-size change clears the bitmap,
140
+ // so it resets the pad (discarding a committed signature) rather than leaving
141
+ // a posted file that no longer matches what is shown. Only a real change
142
+ // counts: a re-run at the same size (a parent re-spreading a fresh props
143
+ // object) leaves the bitmap intact and must not discard the signature.
144
+ let paintedFor: { canvas: HTMLCanvasElement; size: string } | null = null;
145
+ $effect(() => {
146
+ const canvas = canvasEl;
147
+ const size = `${width}x${height}`;
148
+ if (!canvas || (paintedFor?.canvas === canvas && paintedFor.size === size)) {
149
+ return;
150
+ }
151
+ paintedFor = { canvas, size };
152
+ untrack(resetPad);
153
+ });
154
+
155
+ $effect(() => {
156
+ if (strategy !== 'formdata-event' || typeof document === 'undefined') return;
157
+ return attachFormDataFallback(document, () =>
158
+ name && fieldEl
159
+ ? { form: fieldEl.form, name, file: committedFile, source: fieldEl }
160
+ : null,
161
+ );
162
+ });
163
+
164
+ // A reset of the owning form (`form.reset()`, a reset button, `enhance`'s
165
+ // `update()` after a success) empties a native file input, so it discards the
166
+ // signature exactly as "Clear" does: committed or still being drawn, and even
167
+ // while `disabled`, as a reset empties a disabled native input too.
168
+ $effect(() => {
169
+ if (typeof document === 'undefined') return;
170
+ return attachFormResetListener(
171
+ document,
172
+ () => fieldEl?.form ?? rootEl?.closest('form'),
173
+ resetPad,
174
+ );
175
+ });
176
+
177
+ function canvasPoint(event: PointerEvent): { x: number; y: number } {
178
+ if (!canvasEl) return { x: 0, y: 0 };
179
+ return mapPointerToCanvasPoint(
180
+ event.clientX,
181
+ event.clientY,
182
+ canvasEl.getBoundingClientRect(),
183
+ canvasEl.width,
184
+ canvasEl.height,
185
+ );
186
+ }
187
+
188
+ function drawSegment(
189
+ from: { x: number; y: number },
190
+ to: { x: number; y: number },
191
+ ): void {
192
+ const context = canvasEl?.getContext('2d');
193
+ if (!context) return;
194
+ context.lineCap = 'round';
195
+ context.lineJoin = 'round';
196
+ context.lineWidth = 4;
197
+ context.strokeStyle = SIGNATURE_INK_COLOR;
198
+ context.beginPath();
199
+ context.moveTo(from.x, from.y);
200
+ context.lineTo(to.x, to.y);
201
+ context.stroke();
202
+ }
203
+
204
+ function handlePointerDown(event: PointerEvent): void {
205
+ if (disabled || encoding || padState === 'committed') return;
206
+ if (!isAcceptedPointerType(event.pointerType, stylusOnly)) return;
207
+ event.preventDefault();
208
+ activePointerId = event.pointerId;
209
+ lastPoint = canvasPoint(event);
210
+ canvasEl?.setPointerCapture?.(event.pointerId);
211
+ }
212
+
213
+ function handlePointerMove(event: PointerEvent): void {
214
+ if (activePointerId !== event.pointerId) return;
215
+ if (disabled || encoding || padState === 'committed') return;
216
+ event.preventDefault();
217
+ const point = canvasPoint(event);
218
+ if (lastPoint) drawSegment(lastPoint, point);
219
+ lastPoint = point;
220
+ padState = 'signed';
221
+ }
222
+
223
+ function endStroke(event: PointerEvent): void {
224
+ if (activePointerId !== event.pointerId) return;
225
+ activePointerId = null;
226
+ lastPoint = null;
227
+ }
228
+
229
+ function writeField(file: File | null): void {
230
+ if (!fieldEl || !name || strategy !== 'data-transfer') return;
231
+ if (!file) {
232
+ clearFileInput(fieldEl);
233
+ return;
234
+ }
235
+ if (!assignFileWithDataTransfer(fieldEl, file)) {
236
+ strategy = detectNativeFileFieldStrategy({
237
+ FormDataEvent: (globalThis as Record<string, unknown>).FormDataEvent,
238
+ });
239
+ }
240
+ }
241
+
242
+ function clear(): void {
243
+ if (!disabled) resetPad();
244
+ }
245
+
246
+ function resetPad(): void {
247
+ const wasCommitted = padState === 'committed';
248
+ encodeRequest += 1;
249
+ encoding = false;
250
+ activePointerId = null;
251
+ lastPoint = null;
252
+ paintPaper();
253
+ padState = 'empty';
254
+ if (wasCommitted) {
255
+ committedFile = null;
256
+ writeField(null);
257
+ onClear?.();
258
+ }
259
+ }
260
+
261
+ function accept(): void {
262
+ const canvas = canvasEl;
263
+ if (disabled || encoding || padState !== 'signed' || !canvas) return;
264
+ encoding = true;
265
+ activePointerId = null;
266
+ lastPoint = null;
267
+ const request = ++encodeRequest;
268
+ const dataUrl = canvas.toDataURL('image/png');
269
+ canvas.toBlob((blob) => {
270
+ // A Clear (or resize) during encoding superseded this request.
271
+ if (request !== encodeRequest) return;
272
+ encoding = false;
273
+ if (!blob) return;
274
+ committedFile = new File([blob], fileName, { type: 'image/png' });
275
+ writeField(committedFile);
276
+ padState = 'committed';
277
+ onCapture?.({ blob, dataUrl });
278
+ }, 'image/png');
279
+ }
280
+
281
+ useControlRegistration(() => {
282
+ const root = rootEl;
283
+ if (!root || interaction === false) return false;
284
+ return {
285
+ controlId,
286
+ subject: interaction?.subject,
287
+ metadata: {
288
+ kind: 'file',
289
+ label: text.region,
290
+ description: interaction?.description,
291
+ sensitivity: interaction?.sensitivity ?? 'sensitive',
292
+ readable: false,
293
+ writable: false,
294
+ capabilities: ['focus', 'reveal', 'highlight', 'explain'],
295
+ },
296
+ focus: () => focusControl(root),
297
+ reveal: () => revealControl(root),
298
+ highlight: (durationMs) => highlightControl(root, durationMs),
299
+ getState: () => ({ disabled }),
300
+ };
301
+ });
302
+ </script>
303
+
304
+ <div
305
+ bind:this={rootEl}
306
+ id={resolvedId}
307
+ class="signature-pad {className}"
308
+ class:disabled
309
+ role="group"
310
+ aria-label={text.region}
311
+ data-state={padState}
312
+ data-smrt-file-field={name ? strategy : undefined}
313
+ data-smrt-control={controlId}
314
+ data-smrt-form={interactionContext?.formId}
315
+ data-smrt-subject-type={interaction === false ? undefined : interaction?.subject?.type}
316
+ data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id}
317
+ >
318
+ <canvas
319
+ bind:this={canvasEl}
320
+ {width}
321
+ {height}
322
+ aria-label={padState === 'empty' ? text.canvasEmpty : text.canvasSigned}
323
+ aria-describedby={hintId}
324
+ data-stylus-only={stylusOnly}
325
+ style:background-color={SIGNATURE_PAPER_COLOR}
326
+ onpointerdown={handlePointerDown}
327
+ onpointermove={handlePointerMove}
328
+ onpointerup={endStroke}
329
+ onpointercancel={endStroke}
330
+ ></canvas>
331
+
332
+ <p id={hintId} class="hint">{stylusOnly ? text.hintStylus : text.hintAny}</p>
333
+ <p class="message" role="status">{padState === 'committed' ? text.committed : ''}</p>
334
+
335
+ <div class="actions">
336
+ <button type="button" class="action" onclick={clear} {disabled}>{text.clear}</button>
337
+ <button
338
+ type="button"
339
+ class="action primary"
340
+ onclick={accept}
341
+ disabled={disabled || encoding || padState !== 'signed'}
342
+ >
343
+ {text.useSignature}
344
+ </button>
345
+ </div>
346
+
347
+ {#if name}
348
+ <input
349
+ bind:this={fieldEl}
350
+ class="field"
351
+ type="file"
352
+ hidden
353
+ tabindex="-1"
354
+ name={strategy === 'data-transfer' ? name : undefined}
355
+ data-smrt-capture-field
356
+ />
357
+ {/if}
358
+ </div>
359
+
360
+ <style>
361
+ .signature-pad {
362
+ display: grid;
363
+ gap: var(--smrt-spacing-3);
364
+ width: 100%;
365
+ max-width: 40rem;
366
+ color: var(--smrt-color-on-surface);
367
+ }
368
+ canvas {
369
+ display: block;
370
+ width: 100%;
371
+ height: auto;
372
+ border: 2px solid var(--smrt-color-outline);
373
+ border-radius: var(--smrt-radius-medium);
374
+ cursor: crosshair;
375
+ touch-action: none;
376
+ }
377
+ .disabled canvas,
378
+ [data-state='committed'] canvas {
379
+ cursor: default;
380
+ }
381
+ .disabled canvas {
382
+ opacity: 0.5;
383
+ }
384
+ .hint,
385
+ .message {
386
+ margin: 0;
387
+ }
388
+ .hint {
389
+ font: var(--smrt-typography-body-medium-font);
390
+ color: var(--smrt-color-on-surface-variant);
391
+ }
392
+ .message {
393
+ font: var(--smrt-typography-body-large-font);
394
+ }
395
+ .message:empty,
396
+ .field {
397
+ display: none;
398
+ }
399
+ .actions {
400
+ display: flex;
401
+ flex-wrap: wrap;
402
+ gap: var(--smrt-spacing-3);
403
+ }
404
+ .action {
405
+ flex: 1 1 auto;
406
+ /* Capture is a touch flow: its actions are touch targets at every
407
+ density, on the shared scale (smrt#3252). */
408
+ min-width: var(--smrt-touch-target-min, 48px);
409
+ min-height: var(--smrt-touch-target-min, 48px);
410
+ padding: 0 var(--smrt-spacing-4);
411
+ border: 1px solid var(--smrt-color-outline);
412
+ border-radius: var(--smrt-radius-small);
413
+ background: var(--smrt-color-surface);
414
+ color: var(--smrt-color-on-surface);
415
+ font: var(--smrt-typography-label-large-font);
416
+ cursor: pointer;
417
+ }
418
+ .action.primary {
419
+ flex-grow: 2;
420
+ border-color: var(--smrt-color-primary);
421
+ background: var(--smrt-color-primary);
422
+ color: var(--smrt-color-on-primary);
423
+ }
424
+ .action:focus-visible {
425
+ outline: 2px solid var(--smrt-color-primary);
426
+ outline-offset: 2px;
427
+ }
428
+ .action:disabled {
429
+ opacity: 0.5;
430
+ cursor: not-allowed;
431
+ }
432
+ :global(.signature-pad[data-smrt-highlighted='true']) {
433
+ outline: 3px solid var(--smrt-color-tertiary);
434
+ outline-offset: 4px;
435
+ }
436
+ </style>
@@ -0,0 +1,32 @@
1
+ import type { ControlInteractionOptions } from './control-interaction.js';
2
+ import type { CaptureResult, SignaturePadLabels } from './types.js';
3
+ export interface Props {
4
+ /** Accept pen input only; the caller resolves this rule. */
5
+ stylusOnly?: boolean;
6
+ /** Ignores input and disables both actions. */
7
+ disabled?: boolean;
8
+ /** Called when the user commits a signature with "Use signature". */
9
+ onCapture?: (result: CaptureResult) => void;
10
+ /** Called when a committed signature is discarded by "Clear" or a form reset. */
11
+ onClear?: () => void;
12
+ /** Field name: the committed PNG posts as a file in a native form. */
13
+ name?: string;
14
+ /** Posted file name. */
15
+ fileName?: string;
16
+ /** Canvas backing width in pixels (the display width is responsive). */
17
+ width?: number;
18
+ /** Canvas backing height in pixels. */
19
+ height?: number;
20
+ /** Text overrides; defaults come from the `ui` i18n catalog. */
21
+ labels?: Partial<SignaturePadLabels>;
22
+ /** DOM id of the component root. */
23
+ id?: string;
24
+ /** CSS class to apply to the component root. */
25
+ class?: string;
26
+ /** Interaction options or false to disable registration. */
27
+ interaction?: ControlInteractionOptions | false;
28
+ }
29
+ declare const SignaturePad: import("svelte").Component<Props, {}, "">;
30
+ type SignaturePad = ReturnType<typeof SignaturePad>;
31
+ export default SignaturePad;
32
+ //# sourceMappingURL=SignaturePad.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SignaturePad.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/SignaturePad.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAgB1E,OAAO,KAAK,EACV,aAAa,EACb,kBAAkB,EAEnB,MAAM,YAAY,CAAC;AAIpB,MAAM,WAAW,KAAK;IACpB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qEAAqE;IACrE,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAC5C,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACrC,oCAAoC;IACpC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;CACjD;AAqQD,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -20,6 +20,8 @@ export interface Props
20
20
  HTMLInputAttributes,
21
21
  'type' | 'class' | 'checked' | 'role' | 'size'
22
22
  > {
23
+ /** Target density; omit to inherit the ThemeProvider setting. */
24
+ density?: 'comfortable' | 'touch';
23
25
  /** Whether the switch is checked. */
24
26
  checked?: boolean;
25
27
  /** Text displayed next to the switch. */
@@ -48,6 +50,7 @@ let {
48
50
  interaction,
49
51
  onchange,
50
52
  'aria-label': ariaLabel,
53
+ density,
51
54
  ...rest
52
55
  }: Props = $props();
53
56
  const instanceId = $props.id();
@@ -113,7 +116,7 @@ useControlRegistration(() => {
113
116
  });
114
117
  </script>
115
118
 
116
- <label class="switch switch--{size} {className}" class:switch--disabled={disabled} data-smrt-hit-target data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
119
+ <label data-density={density} class="switch switch--{size} {className}" class:switch--disabled={disabled} data-smrt-hit-target data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
117
120
  data-smrt-subject-type={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.type}
118
121
  data-smrt-subject-id={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.id}>
119
122
  {#if label && labelPosition === 'left'}<span class="switch__label">{label}</span>{/if}
@@ -146,4 +149,8 @@ useControlRegistration(() => {
146
149
  .switch--lg .switch__track { transform: scale(1.15); margin-inline: .2rem; }
147
150
  .switch[data-smrt-highlighted='true'] { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; border-radius: var(--smrt-radius-full); }
148
151
  @media (prefers-reduced-motion: reduce) { .switch__track, .switch__thumb { transition: none; } }
152
+
153
+ .switch[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
154
+ .switch[data-density='comfortable'] { --smrt-control-target-min: 0px; }
155
+ .switch { min-height: var(--smrt-control-target-min, 0px); min-width: var(--smrt-control-target-min, 0px); }
149
156
  </style>
@@ -1,6 +1,8 @@
1
1
  import type { HTMLInputAttributes } from 'svelte/elements';
2
2
  import type { ControlInteractionOptions } from './control-interaction.js';
3
3
  export interface Props extends Omit<HTMLInputAttributes, 'type' | 'class' | 'checked' | 'role' | 'size'> {
4
+ /** Target density; omit to inherit the ThemeProvider setting. */
5
+ density?: 'comfortable' | 'touch';
4
6
  /** Whether the switch is checked. */
5
7
  checked?: boolean;
6
8
  /** Text displayed next to the switch. */
@@ -1 +1 @@
1
- {"version":3,"file":"Switch.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Switch.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAM3D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAW1E,MAAM,WAAW,KACf,SAAQ,IAAI,CACV,mBAAmB,EACnB,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAC/C;IACD,qCAAqC;IACrC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,yCAAyC;IACzC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;CACjD;AAuGD,QAAA,MAAM,MAAM,kDAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"Switch.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Switch.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAM3D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAW1E,MAAM,WAAW,KACf,SAAQ,IAAI,CACV,mBAAmB,EACnB,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAC/C;IACD,iEAAiE;IACjE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAClC,qCAAqC;IACrC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,yCAAyC;IACzC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;CACjD;AAwGD,QAAA,MAAM,MAAM,kDAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
@@ -15,6 +15,8 @@ import { tryGetFormGroupContext } from './form-group-context.js';
15
15
  import { useControlRegistration } from './use-control-registration.svelte.js';
16
16
 
17
17
  export interface Props extends Omit<HTMLTextareaAttributes, 'class' | 'value'> {
18
+ /** Target density; omit to inherit the ThemeProvider setting. */
19
+ density?: 'comfortable' | 'touch';
18
20
  /** The text content of the textarea. */
19
21
  value?: string;
20
22
  /** Number of visible text rows. */
@@ -39,6 +41,7 @@ let {
39
41
  'aria-label': ariaLabel,
40
42
  'aria-describedby': ariaDescribedby,
41
43
  'aria-invalid': ariaInvalid,
44
+ density,
42
45
  ...rest
43
46
  }: Props = $props();
44
47
 
@@ -133,7 +136,7 @@ export function getElement(): HTMLTextAreaElement | null {
133
136
  {rows}
134
137
  aria-describedby={resolvedDescribedBy}
135
138
  aria-invalid={resolvedInvalid}
136
- class="textarea {className}"
139
+ data-density={density} class="textarea {className}"
137
140
  data-smrt-control={resolvedControlId}
138
141
  data-smrt-form={controlInteraction?.formId}
139
142
  data-smrt-subject-type={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.type}
@@ -194,4 +197,8 @@ export function getElement(): HTMLTextAreaElement | null {
194
197
  transition: none;
195
198
  }
196
199
  }
200
+
201
+ .textarea[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
202
+ .textarea[data-density='comfortable'] { --smrt-control-target-min: 0px; }
203
+ .textarea { min-height: max(80px, var(--smrt-control-target-min, 0px)); }
197
204
  </style>
@@ -1,6 +1,8 @@
1
1
  import type { HTMLTextareaAttributes } from 'svelte/elements';
2
2
  import type { ControlInteractionOptions } from './control-interaction.js';
3
3
  export interface Props extends Omit<HTMLTextareaAttributes, 'class' | 'value'> {
4
+ /** Target density; omit to inherit the ThemeProvider setting. */
5
+ density?: 'comfortable' | 'touch';
4
6
  /** The text content of the textarea. */
5
7
  value?: string;
6
8
  /** Number of visible text rows. */
@@ -1 +1 @@
1
- {"version":3,"file":"Textarea.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Textarea.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAM9D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAU1E,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,sBAAsB,EAAE,OAAO,GAAG,OAAO,CAAC;IAC5E,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;CACjD;AAgHD,QAAA,MAAM,QAAQ;iBAfK,IAAI;sBAIC,mBAAmB,GAAG,IAAI;WAWI,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"Textarea.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Textarea.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAM9D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAU1E,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,sBAAsB,EAAE,OAAO,GAAG,OAAO,CAAC;IAC5E,iEAAiE;IACjE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAClC,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;CACjD;AAiHD,QAAA,MAAM,QAAQ;iBAfK,IAAI;sBAIC,mBAAmB,GAAG,IAAI;WAWI,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}