@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
|
@@ -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;
|
|
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;
|
|
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
|
|
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"}
|
package/dist/i18n/strings.ui.js
CHANGED
|
@@ -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',
|
package/dist/styles/tokens.css
CHANGED
|
@@ -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
|
|
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));
|