@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.
- package/AGENTS.md +11 -1
- package/README.md +300 -2
- package/dist/components/data/DataTable.svelte +60 -15
- package/dist/components/data/DataTable.svelte.d.ts.map +1 -1
- package/dist/components/data/DataTableResponsive.d.ts +4 -0
- package/dist/components/data/DataTableResponsive.d.ts.map +1 -0
- package/dist/components/data/DataTableResponsive.js +22 -0
- package/dist/components/data/__tests__/DataTableResponsive.test.js +80 -0
- package/dist/components/data/__tests__/responsive-table.fixture.svelte +29 -0
- package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts +7 -0
- package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts.map +1 -0
- package/dist/components/data/types.d.ts +8 -0
- package/dist/components/data/types.d.ts.map +1 -1
- package/dist/components/display/DateDisplay.svelte +76 -14
- package/dist/components/display/DateDisplay.svelte.d.ts +2 -0
- package/dist/components/display/DateDisplay.svelte.d.ts.map +1 -1
- package/dist/components/display/Icon.svelte +3 -16
- package/dist/components/display/Icon.svelte.d.ts +0 -9
- package/dist/components/display/Icon.svelte.d.ts.map +1 -1
- package/dist/components/display/StatusBadge.svelte +29 -2
- package/dist/components/display/StatusBadge.svelte.d.ts +4 -2
- package/dist/components/display/StatusBadge.svelte.d.ts.map +1 -1
- package/dist/components/display/__tests__/DateDisplay.test.js +82 -1
- package/dist/components/display/__tests__/Icon.test.js +132 -2
- package/dist/components/display/__tests__/StatusBadge.test.js +78 -0
- package/dist/components/display/icons.svelte.d.ts +11 -0
- package/dist/components/display/icons.svelte.d.ts.map +1 -0
- package/dist/components/display/icons.svelte.js +60 -0
- package/dist/components/display/index.d.ts +3 -1
- package/dist/components/display/index.d.ts.map +1 -1
- package/dist/components/display/index.js +1 -0
- package/dist/components/display/types.d.ts +2 -0
- package/dist/components/display/types.d.ts.map +1 -1
- package/dist/components/feedback/ConfirmDialog.svelte +59 -48
- package/dist/components/feedback/ConfirmDialog.svelte.d.ts +14 -2
- package/dist/components/feedback/ConfirmDialog.svelte.d.ts.map +1 -1
- package/dist/components/feedback/ToastViewport.svelte +156 -8
- package/dist/components/feedback/ToastViewport.svelte.d.ts +5 -1
- package/dist/components/feedback/ToastViewport.svelte.d.ts.map +1 -1
- package/dist/components/feedback/__tests__/ConfirmDialog.test.js +38 -20
- package/dist/components/feedback/__tests__/confirm-message.fixture.svelte +9 -0
- package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts +19 -0
- package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts.map +1 -0
- package/dist/components/feedback/__tests__/toast.test.js +142 -1
- package/dist/components/feedback/index.d.ts +1 -1
- package/dist/components/feedback/index.d.ts.map +1 -1
- package/dist/components/feedback/toast.d.ts +13 -1
- package/dist/components/feedback/toast.d.ts.map +1 -1
- package/dist/components/feedback/toast.js +51 -10
- package/dist/components/forms/CameraCapture.svelte +619 -0
- package/dist/components/forms/CameraCapture.svelte.d.ts +42 -0
- package/dist/components/forms/CameraCapture.svelte.d.ts.map +1 -0
- package/dist/components/forms/Checkbox.svelte +8 -1
- package/dist/components/forms/Checkbox.svelte.d.ts +2 -0
- package/dist/components/forms/Checkbox.svelte.d.ts.map +1 -1
- package/dist/components/forms/Combobox.svelte +40 -0
- package/dist/components/forms/Combobox.svelte.d.ts.map +1 -1
- package/dist/components/forms/Form.svelte +5 -0
- package/dist/components/forms/Form.svelte.d.ts +1 -0
- package/dist/components/forms/Form.svelte.d.ts.map +1 -1
- package/dist/components/forms/Input.svelte +8 -1
- package/dist/components/forms/Input.svelte.d.ts +2 -0
- package/dist/components/forms/Input.svelte.d.ts.map +1 -1
- package/dist/components/forms/Listbox.svelte +39 -1
- package/dist/components/forms/Listbox.svelte.d.ts.map +1 -1
- package/dist/components/forms/MultiSelect.svelte +41 -1
- package/dist/components/forms/MultiSelect.svelte.d.ts.map +1 -1
- package/dist/components/forms/Radio.svelte +1 -0
- package/dist/components/forms/RadioGroup.svelte +7 -1
- package/dist/components/forms/RadioGroup.svelte.d.ts +2 -0
- package/dist/components/forms/RadioGroup.svelte.d.ts.map +1 -1
- package/dist/components/forms/SegmentedControl.svelte +152 -11
- package/dist/components/forms/SegmentedControl.svelte.d.ts +4 -0
- package/dist/components/forms/SegmentedControl.svelte.d.ts.map +1 -1
- package/dist/components/forms/Select.svelte +8 -1
- package/dist/components/forms/Select.svelte.d.ts +2 -0
- package/dist/components/forms/Select.svelte.d.ts.map +1 -1
- package/dist/components/forms/SignaturePad.svelte +436 -0
- package/dist/components/forms/SignaturePad.svelte.d.ts +32 -0
- package/dist/components/forms/SignaturePad.svelte.d.ts.map +1 -0
- package/dist/components/forms/Switch.svelte +8 -1
- package/dist/components/forms/Switch.svelte.d.ts +2 -0
- package/dist/components/forms/Switch.svelte.d.ts.map +1 -1
- package/dist/components/forms/Textarea.svelte +8 -1
- package/dist/components/forms/Textarea.svelte.d.ts +2 -0
- package/dist/components/forms/Textarea.svelte.d.ts.map +1 -1
- package/dist/components/forms/__tests__/CameraCapture.test.js +504 -0
- package/dist/components/forms/__tests__/Combobox.test.js +279 -12
- package/dist/components/forms/__tests__/Form.test.js +46 -0
- package/dist/components/forms/__tests__/Listbox.test.js +303 -0
- package/dist/components/forms/__tests__/MultiSelect.test.js +281 -0
- package/dist/components/forms/__tests__/SignaturePad.test.js +338 -0
- package/dist/components/forms/__tests__/camera-capture-session.test.js +207 -0
- package/dist/components/forms/__tests__/capture-form-retry.test.js +166 -0
- package/dist/components/forms/__tests__/capture-form.fixture.svelte +38 -0
- package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts +10 -0
- package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/capture-test-env.d.ts +70 -0
- package/dist/components/forms/__tests__/capture-test-env.d.ts.map +1 -0
- package/dist/components/forms/__tests__/capture-test-env.js +122 -0
- package/dist/components/forms/__tests__/capture-touch.fixture.svelte +15 -0
- package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts +8 -0
- package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/combobox-form.fixture.svelte +29 -0
- package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts +14 -0
- package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-attachment.fixture.svelte +28 -0
- package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts +11 -0
- package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-retry-form.fixture.svelte +31 -0
- package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts +13 -0
- package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-retry-form.test.js +107 -0
- package/dist/components/forms/__tests__/hidden-controls-form-retry.test.js +69 -0
- package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte +29 -0
- package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts +9 -0
- package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/listbox-form.fixture.svelte +27 -0
- package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts +13 -0
- package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/multiselect-form.fixture.svelte +27 -0
- package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts +13 -0
- package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/native-file-field.test.js +177 -0
- package/dist/components/forms/__tests__/segmented-posting.fixture.svelte +28 -0
- package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts +12 -0
- package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/segmented-posting.test.js +92 -0
- package/dist/components/forms/__tests__/signature-pad-logic.test.js +54 -0
- package/dist/components/forms/__tests__/touch-density.fixture.svelte +34 -0
- package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts +8 -0
- package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/touch-density.test.js +66 -0
- package/dist/components/forms/camera-capture-session.d.ts +81 -0
- package/dist/components/forms/camera-capture-session.d.ts.map +1 -0
- package/dist/components/forms/camera-capture-session.js +130 -0
- package/dist/components/forms/form-retry/__tests__/boundaries.test.js +23 -0
- package/dist/components/forms/form-retry/__tests__/content-digest.test.js +70 -0
- package/dist/components/forms/form-retry/__tests__/controller.test.js +802 -0
- package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts +82 -0
- package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts.map +1 -0
- package/dist/components/forms/form-retry/__tests__/fake-kit.js +180 -0
- package/dist/components/forms/form-retry/__tests__/form-draft.test.js +169 -0
- package/dist/components/forms/form-retry/__tests__/ssr.test.js +37 -0
- package/dist/components/forms/form-retry/__tests__/storage.d.ts +26 -0
- package/dist/components/forms/form-retry/__tests__/storage.d.ts.map +1 -0
- package/dist/components/forms/form-retry/__tests__/storage.js +63 -0
- package/dist/components/forms/form-retry/__tests__/submission-key.test.js +236 -0
- package/dist/components/forms/form-retry/__tests__/submitted-draft.test.js +334 -0
- package/dist/components/forms/form-retry/content-digest.d.ts +54 -0
- package/dist/components/forms/form-retry/content-digest.d.ts.map +1 -0
- package/dist/components/forms/form-retry/content-digest.js +169 -0
- package/dist/components/forms/form-retry/controller.d.ts +214 -0
- package/dist/components/forms/form-retry/controller.d.ts.map +1 -0
- package/dist/components/forms/form-retry/controller.js +404 -0
- package/dist/components/forms/form-retry/form-draft.d.ts +68 -0
- package/dist/components/forms/form-retry/form-draft.d.ts.map +1 -0
- package/dist/components/forms/form-retry/form-draft.js +161 -0
- package/dist/components/forms/form-retry/index.d.ts +19 -0
- package/dist/components/forms/form-retry/index.d.ts.map +1 -0
- package/dist/components/forms/form-retry/index.js +16 -0
- package/dist/components/forms/form-retry/submission-key.d.ts +244 -0
- package/dist/components/forms/form-retry/submission-key.d.ts.map +1 -0
- package/dist/components/forms/form-retry/submission-key.js +342 -0
- package/dist/components/forms/form-retry/submitted-draft.d.ts +112 -0
- package/dist/components/forms/form-retry/submitted-draft.d.ts.map +1 -0
- package/dist/components/forms/form-retry/submitted-draft.js +270 -0
- package/dist/components/forms/form-retry/types.d.ts +84 -0
- package/dist/components/forms/form-retry/types.d.ts.map +1 -0
- package/dist/components/forms/form-retry/types.js +13 -0
- package/dist/components/forms/index.d.ts +7 -1
- package/dist/components/forms/index.d.ts.map +1 -1
- package/dist/components/forms/index.js +8 -0
- package/dist/components/forms/native-file-field.d.ts +94 -0
- package/dist/components/forms/native-file-field.d.ts.map +1 -0
- package/dist/components/forms/native-file-field.js +147 -0
- package/dist/components/forms/signature-pad-logic.d.ts +50 -0
- package/dist/components/forms/signature-pad-logic.d.ts.map +1 -0
- package/dist/components/forms/signature-pad-logic.js +48 -0
- package/dist/components/forms/types.d.ts +41 -0
- package/dist/components/forms/types.d.ts.map +1 -1
- package/dist/components/nav/FilterChips.svelte +8 -1
- package/dist/components/nav/FilterChips.svelte.d.ts +2 -0
- package/dist/components/nav/FilterChips.svelte.d.ts.map +1 -1
- package/dist/components/ui/Button.svelte +9 -2
- package/dist/components/ui/Button.svelte.d.ts +2 -0
- package/dist/components/ui/Button.svelte.d.ts.map +1 -1
- package/dist/i18n/strings.ui.d.ts +24 -0
- package/dist/i18n/strings.ui.d.ts.map +1 -1
- package/dist/i18n/strings.ui.js +26 -0
- package/dist/styles/tokens.css +2 -0
- package/dist/svelte/__tests__/playground.test.js +2 -1
- package/dist/svelte/playground/CapturePreview.svelte +56 -0
- package/dist/svelte/playground/CapturePreview.svelte.d.ts +4 -0
- package/dist/svelte/playground/CapturePreview.svelte.d.ts.map +1 -0
- package/dist/svelte/playground.d.ts.map +1 -1
- package/dist/svelte/playground.js +10 -0
- package/dist/themes/ThemeProvider.svelte +9 -0
- package/dist/themes/ThemeProvider.svelte.d.ts +2 -0
- package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
- package/dist/themes/css-generator.d.ts.map +1 -1
- package/dist/themes/css-generator.js +3 -0
- package/dist/themes/styles/glass.css +4 -0
- package/dist/themes/styles/happyvertical.css +4 -0
- package/dist/themes/styles/material.css +4 -0
- package/dist/themes/styles/smrt.css +4 -0
- package/dist/themes/styles/studio.css +4 -0
- 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;
|
|
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;
|
|
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;
|
|
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"}
|