@aiaiai-pt/design-system 0.44.1 → 0.46.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/components/ActionFormRenderer.svelte +446 -48
- package/components/ActionFormRenderer.svelte.d.ts +15 -0
- package/components/DateRangePicker.svelte.d.ts +1 -1
- package/components/JsonEditor.svelte +152 -0
- package/components/JsonEditor.svelte.d.ts +44 -0
- package/components/LayoutStackedDefault.svelte +36 -3
- package/components/LayoutStackedDefault.svelte.d.ts +1 -1
- package/components/MoneyInput.svelte +219 -0
- package/components/MoneyInput.svelte.d.ts +52 -0
- package/components/MultiSelectCombobox.svelte +140 -0
- package/components/MultiSelectCombobox.svelte.d.ts +62 -0
- package/components/Tag.svelte +4 -0
- package/components/action-form-renderer-layouts.d.ts +20 -0
- package/components/action-form-renderer-layouts.ts +40 -3
- package/components/action-form-renderer-payload.d.ts +12 -0
- package/components/action-form-renderer-payload.ts +46 -3
- package/components/action-form-renderer-relationships.d.ts +20 -0
- package/components/action-form-renderer-relationships.ts +77 -0
- package/components/action-form-renderer-widgets.d.ts +39 -0
- package/components/action-form-renderer-widgets.ts +127 -0
- package/components/action-form-visibility.d.ts +48 -0
- package/components/action-form-visibility.ts +171 -0
- package/components/index.d.ts +4 -0
- package/components/index.js +11 -0
- package/components/json-editor-contract.d.ts +11 -0
- package/components/json-editor-contract.ts +33 -0
- package/package.json +17 -1
|
@@ -22,6 +22,10 @@ type ActionSchema = {
|
|
|
22
22
|
sections?: Array<{
|
|
23
23
|
key?: string;
|
|
24
24
|
label?: string;
|
|
25
|
+
order?: number | null;
|
|
26
|
+
columns?: number | null;
|
|
27
|
+
/** Read defensively (hosts carry concrete precondition types). */
|
|
28
|
+
visible_when?: unknown;
|
|
25
29
|
parameters?: Entity[];
|
|
26
30
|
}>;
|
|
27
31
|
parameters?: Entity[];
|
|
@@ -65,6 +69,17 @@ interface Props {
|
|
|
65
69
|
* transport/auth/tenancy (the uploadFile seam pattern — the DS stays
|
|
66
70
|
* presentation-only). Omitted → object_reference params render disabled. */
|
|
67
71
|
searchEntities?: (typeCode: string, query: string, filter?: Record<string, string>) => Promise<SelectOption[]>;
|
|
72
|
+
/** Edit-time label hydration for relationship params (#34): receives the
|
|
73
|
+
* target entity type code and the currently-linked ids, returns
|
|
74
|
+
* `{value, label}` items so the picker renders labels, never raw ids.
|
|
75
|
+
* Same philosophy as searchEntities — the CONSUMER owns transport/auth;
|
|
76
|
+
* omitted → the picker falls back to showing the ids. Best-effort: a
|
|
77
|
+
* failed hydration never blocks the form. */
|
|
78
|
+
hydrateEntities?: (typeCode: string, ids: string[]) => Promise<SelectOption[]>;
|
|
79
|
+
/** #41 — per-field server-error binding: `{param_key: message}` rendered
|
|
80
|
+
* as each field's inline error (the V2a adapter re-binds proxy 4xx field
|
|
81
|
+
* errors here). Display-only — the HOST owns when errors set and clear. */
|
|
82
|
+
fieldErrors?: Record<string, string>;
|
|
68
83
|
/** Environment-specific captcha (e.g. Turnstile), rendered above the button
|
|
69
84
|
* in submit modes. */
|
|
70
85
|
captcha?: Snippet;
|
|
@@ -35,7 +35,7 @@ declare const DateRangePicker: import("svelte").Component<{
|
|
|
35
35
|
onchange?: any;
|
|
36
36
|
id?: any;
|
|
37
37
|
class?: string;
|
|
38
|
-
} & Record<string, any>, {}, "
|
|
38
|
+
} & Record<string, any>, {}, "end" | "start">;
|
|
39
39
|
type $$ComponentProps = {
|
|
40
40
|
start?: any;
|
|
41
41
|
end?: any;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component JsonEditor
|
|
3
|
+
|
|
4
|
+
JSON field over the DS CodeEditor (#38, atelier#669 V1) with an explicit
|
|
5
|
+
invalid-state contract: invalid text NEVER silently drops or half-parses —
|
|
6
|
+
the field surfaces the parse error, emits NOTHING, and reports invalidity
|
|
7
|
+
through `oninvalid` so the host form can block submit. Empty and `{}` are
|
|
8
|
+
distinct (empty → null).
|
|
9
|
+
|
|
10
|
+
@example
|
|
11
|
+
<JsonEditor
|
|
12
|
+
label="METADATA"
|
|
13
|
+
value={{ kind: 'sensor' }}
|
|
14
|
+
onchange={(v) => save(v)}
|
|
15
|
+
oninvalid={(message) => (blocked = message !== null)}
|
|
16
|
+
/>
|
|
17
|
+
-->
|
|
18
|
+
<script module>
|
|
19
|
+
let _jsonEditorUid = 0;
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<script>
|
|
23
|
+
import CodeEditor from './CodeEditor.svelte';
|
|
24
|
+
import { evaluateJsonDraft, formatJsonValue } from './json-editor-contract';
|
|
25
|
+
|
|
26
|
+
let {
|
|
27
|
+
/** @type {unknown} — the current JSON value (object/array/scalar/null) */
|
|
28
|
+
value = null,
|
|
29
|
+
/** @type {string | undefined} */
|
|
30
|
+
label = undefined,
|
|
31
|
+
/** @type {string | undefined} */
|
|
32
|
+
help = undefined,
|
|
33
|
+
/** @type {string | undefined} — EXTERNAL (server) error; parse errors
|
|
34
|
+
* render through the same chrome automatically */
|
|
35
|
+
error = undefined,
|
|
36
|
+
/** @type {boolean} */
|
|
37
|
+
disabled = false,
|
|
38
|
+
/** @type {boolean} */
|
|
39
|
+
readonly = false,
|
|
40
|
+
/** @type {((value: unknown) => void) | undefined} — fires ONLY on a
|
|
41
|
+
* valid parse (or empty → null); never fires for invalid text */
|
|
42
|
+
onchange = undefined,
|
|
43
|
+
/** @type {((message: string | null) => void) | undefined} — parse-state
|
|
44
|
+
* transitions: a message while the draft is invalid, null when it
|
|
45
|
+
* becomes valid again. The host blocks submit while non-null. */
|
|
46
|
+
oninvalid = undefined,
|
|
47
|
+
/** @type {string} */
|
|
48
|
+
class: className = '',
|
|
49
|
+
...rest
|
|
50
|
+
} = $props();
|
|
51
|
+
|
|
52
|
+
const editorId = `json-editor-${_jsonEditorUid++}`;
|
|
53
|
+
const hintId = `${editorId}-hint`;
|
|
54
|
+
|
|
55
|
+
let text = $state(formatJsonValue(value));
|
|
56
|
+
let parseError = $state(/** @type {string | null} */ (null));
|
|
57
|
+
|
|
58
|
+
// Watch the CodeEditor-bound text: evaluate on every draft change. The
|
|
59
|
+
// pipeline itself is the pure json-editor-contract module (CodeMirror is
|
|
60
|
+
// not edit-drivable under jsdom, so the contract carries the test weight).
|
|
61
|
+
let lastEvaluated = text;
|
|
62
|
+
$effect(() => {
|
|
63
|
+
if (text === lastEvaluated) return;
|
|
64
|
+
lastEvaluated = text;
|
|
65
|
+
const result = evaluateJsonDraft(text);
|
|
66
|
+
if (result.ok) {
|
|
67
|
+
parseError = null;
|
|
68
|
+
oninvalid?.(null);
|
|
69
|
+
onchange?.(result.value);
|
|
70
|
+
} else {
|
|
71
|
+
parseError = result.error;
|
|
72
|
+
oninvalid?.(result.error);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const shownError = $derived(error ?? parseError ?? undefined);
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<div class="json-editor {className}" {...rest}>
|
|
80
|
+
{#if label}
|
|
81
|
+
<span class="json-editor-label" id={`${editorId}-label`}>{label}</span>
|
|
82
|
+
{/if}
|
|
83
|
+
<div
|
|
84
|
+
class="json-editor-surface"
|
|
85
|
+
class:json-editor-error={!!shownError}
|
|
86
|
+
class:json-editor-disabled={disabled}
|
|
87
|
+
aria-labelledby={label ? `${editorId}-label` : undefined}
|
|
88
|
+
aria-describedby={shownError || help ? hintId : undefined}
|
|
89
|
+
>
|
|
90
|
+
<CodeEditor
|
|
91
|
+
bind:value={text}
|
|
92
|
+
language="json"
|
|
93
|
+
readonly={readonly || disabled}
|
|
94
|
+
lineNumbers={false}
|
|
95
|
+
minLines={4}
|
|
96
|
+
maxLines={16}
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
{#if shownError}
|
|
100
|
+
<span id={hintId} class="json-editor-error-text" role="alert">{shownError}</span>
|
|
101
|
+
{:else if help}
|
|
102
|
+
<span id={hintId} class="json-editor-help">{help}</span>
|
|
103
|
+
{/if}
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<style>
|
|
107
|
+
.json-editor {
|
|
108
|
+
display: flex;
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
gap: var(--input-label-gap);
|
|
111
|
+
width: 100%;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.json-editor-label {
|
|
115
|
+
font-family: var(--input-label-font);
|
|
116
|
+
font-size: var(--input-label-size);
|
|
117
|
+
letter-spacing: var(--input-label-tracking);
|
|
118
|
+
color: var(--input-label-color);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.json-editor-surface {
|
|
122
|
+
border: var(--input-border);
|
|
123
|
+
border-radius: var(--input-radius);
|
|
124
|
+
background: var(--input-bg);
|
|
125
|
+
padding: var(--space-xs);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.json-editor-surface:focus-within {
|
|
129
|
+
border: var(--input-border-focus);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.json-editor-error {
|
|
133
|
+
border-color: var(--input-error-border-color);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.json-editor-disabled {
|
|
137
|
+
opacity: 0.5;
|
|
138
|
+
pointer-events: none;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.json-editor-help {
|
|
142
|
+
font-family: var(--input-help-font);
|
|
143
|
+
font-size: var(--input-help-size);
|
|
144
|
+
color: var(--input-help-color);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.json-editor-error-text {
|
|
148
|
+
font-family: var(--input-help-font);
|
|
149
|
+
font-size: var(--input-help-size);
|
|
150
|
+
color: var(--input-error-text);
|
|
151
|
+
}
|
|
152
|
+
</style>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export default JsonEditor;
|
|
2
|
+
type JsonEditor = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* JsonEditor
|
|
8
|
+
*
|
|
9
|
+
* JSON field over the DS CodeEditor (#38, atelier#669 V1) with an explicit
|
|
10
|
+
* invalid-state contract: invalid text NEVER silently drops or half-parses —
|
|
11
|
+
* the field surfaces the parse error, emits NOTHING, and reports invalidity
|
|
12
|
+
* through `oninvalid` so the host form can block submit. Empty and `{}` are
|
|
13
|
+
* distinct (empty → null).
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <JsonEditor
|
|
17
|
+
* label="METADATA"
|
|
18
|
+
* value={{ kind: 'sensor' }}
|
|
19
|
+
* onchange={(v) => save(v)}
|
|
20
|
+
* oninvalid={(message) => (blocked = message !== null)}
|
|
21
|
+
* />
|
|
22
|
+
*/
|
|
23
|
+
declare const JsonEditor: import("svelte").Component<{
|
|
24
|
+
value?: any;
|
|
25
|
+
label?: any;
|
|
26
|
+
help?: any;
|
|
27
|
+
error?: any;
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
readonly?: boolean;
|
|
30
|
+
onchange?: any;
|
|
31
|
+
oninvalid?: any;
|
|
32
|
+
class?: string;
|
|
33
|
+
} & Record<string, any>, {}, "">;
|
|
34
|
+
type $$ComponentProps = {
|
|
35
|
+
value?: any;
|
|
36
|
+
label?: any;
|
|
37
|
+
help?: any;
|
|
38
|
+
error?: any;
|
|
39
|
+
disabled?: boolean;
|
|
40
|
+
readonly?: boolean;
|
|
41
|
+
onchange?: any;
|
|
42
|
+
oninvalid?: any;
|
|
43
|
+
class?: string;
|
|
44
|
+
} & Record<string, any>;
|
|
@@ -5,9 +5,15 @@
|
|
|
5
5
|
* Each section is a labelled <fieldset>, parameters laid out as a
|
|
6
6
|
* single-column flex column. This is the safe fallback for unknown
|
|
7
7
|
* layout keys — see `resolve.ts`.
|
|
8
|
+
*
|
|
9
|
+
* #634 S3 — a section declaring `columns: 2` lays its cells on a
|
|
10
|
+
* two-column grid; `span: full` params (and wide types — geo/file)
|
|
11
|
+
* break out to the full row (the EntityFormSurface two-col precedent).
|
|
12
|
+
* Default (columns 1 / absent) keeps the single-column stack
|
|
13
|
+
* byte-identical.
|
|
8
14
|
*/
|
|
9
15
|
import Card from "./Card.svelte";
|
|
10
|
-
import type
|
|
16
|
+
import { fieldSpansFull, type LayoutSection } from "./action-form-renderer-layouts";
|
|
11
17
|
import type { Snippet } from "svelte";
|
|
12
18
|
|
|
13
19
|
let {
|
|
@@ -23,9 +29,16 @@
|
|
|
23
29
|
{#each sections as section (section.name)}
|
|
24
30
|
<Card variant="flat" class="section" role="group" aria-label={section.name}>
|
|
25
31
|
<h4>{section.name}</h4>
|
|
26
|
-
<div
|
|
32
|
+
<div
|
|
33
|
+
class="rows"
|
|
34
|
+
class:two-col={(section.columns ?? 1) === 2}
|
|
35
|
+
data-columns={section.columns ?? 1}
|
|
36
|
+
>
|
|
27
37
|
{#each section.items as parameter}
|
|
28
|
-
<div
|
|
38
|
+
<div
|
|
39
|
+
class="field-row"
|
|
40
|
+
class:span-full={fieldSpansFull(parameter, section.columns)}
|
|
41
|
+
>
|
|
29
42
|
{@render field(parameter)}
|
|
30
43
|
</div>
|
|
31
44
|
{/each}
|
|
@@ -53,6 +66,26 @@
|
|
|
53
66
|
gap: var(--space-md);
|
|
54
67
|
}
|
|
55
68
|
|
|
69
|
+
/* #634 S3 — two-column density: half-width cells flow the grid,
|
|
70
|
+
span-full cells break out to the whole row. Collapses back to one
|
|
71
|
+
column on narrow viewports (the EntityFormSurface breakpoint). */
|
|
72
|
+
.rows.two-col {
|
|
73
|
+
display: grid;
|
|
74
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
75
|
+
column-gap: var(--space-md);
|
|
76
|
+
row-gap: var(--space-md);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.rows.two-col .field-row.span-full {
|
|
80
|
+
grid-column: 1 / -1;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@media (max-width: 48rem) {
|
|
84
|
+
.rows.two-col {
|
|
85
|
+
grid-template-columns: 1fr;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
56
89
|
.field-row {
|
|
57
90
|
display: flex;
|
|
58
91
|
flex-direction: column;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component MoneyInput
|
|
3
|
+
|
|
4
|
+
Number-semantics input with a currency affordance (#35, atelier#669 V1).
|
|
5
|
+
The WIRE value is always a raw number (or null when empty — empty and 0
|
|
6
|
+
are distinct); locale-aware currency formatting appears while the field is
|
|
7
|
+
NOT being edited; focus switches to the raw editable number.
|
|
8
|
+
Consumes --input-* tokens (mirrors Input's field chrome).
|
|
9
|
+
|
|
10
|
+
@example
|
|
11
|
+
<MoneyInput label="BUDGET" value={1234.5} onchange={(n) => save(n)} />
|
|
12
|
+
|
|
13
|
+
@example Non-default currency
|
|
14
|
+
<MoneyInput label="FEE" currency="USD" locale="en-US" />
|
|
15
|
+
-->
|
|
16
|
+
<script module>
|
|
17
|
+
let _moneyUid = 0;
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<script>
|
|
21
|
+
let {
|
|
22
|
+
/** @type {number | null} — the raw amount; null = empty */
|
|
23
|
+
value = null,
|
|
24
|
+
/** @type {string} — ISO 4217 code for the display affordance */
|
|
25
|
+
currency = 'EUR',
|
|
26
|
+
/** @type {string | undefined} — BCP 47; defaults to the runtime locale */
|
|
27
|
+
locale = undefined,
|
|
28
|
+
/** @type {string | undefined} */
|
|
29
|
+
label = undefined,
|
|
30
|
+
/** @type {string | undefined} */
|
|
31
|
+
placeholder = undefined,
|
|
32
|
+
/** @type {string | undefined} */
|
|
33
|
+
help = undefined,
|
|
34
|
+
/** @type {string | undefined} */
|
|
35
|
+
error = undefined,
|
|
36
|
+
/** @type {'sm' | 'md' | 'lg'} */
|
|
37
|
+
size = 'md',
|
|
38
|
+
/** @type {boolean} */
|
|
39
|
+
disabled = false,
|
|
40
|
+
/** @type {boolean} */
|
|
41
|
+
readonly = false,
|
|
42
|
+
/** @type {string | undefined} */
|
|
43
|
+
id = undefined,
|
|
44
|
+
/** @type {string | undefined} */
|
|
45
|
+
name = undefined,
|
|
46
|
+
/** @type {((value: number | null) => void) | undefined} — fires with the
|
|
47
|
+
* RAW number (never a formatted string); null when cleared/unparseable */
|
|
48
|
+
onchange = undefined,
|
|
49
|
+
/** @type {string} */
|
|
50
|
+
class: className = '',
|
|
51
|
+
...rest
|
|
52
|
+
} = $props();
|
|
53
|
+
|
|
54
|
+
const fallbackId = `money-input-${_moneyUid++}`;
|
|
55
|
+
const inputId = $derived(id ?? fallbackId);
|
|
56
|
+
const hintId = $derived(`${inputId}-hint`);
|
|
57
|
+
const hasHint = $derived(!!error || !!help);
|
|
58
|
+
|
|
59
|
+
let focused = $state(false);
|
|
60
|
+
let draft = $state('');
|
|
61
|
+
|
|
62
|
+
const formatter = $derived(
|
|
63
|
+
new Intl.NumberFormat(locale, { style: 'currency', currency }),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const display = $derived(
|
|
67
|
+
focused
|
|
68
|
+
? draft
|
|
69
|
+
: value === null || value === undefined
|
|
70
|
+
? ''
|
|
71
|
+
: formatter.format(Number(value)),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Parse the operator's raw text onto a number: strips grouping spaces,
|
|
76
|
+
* accepts a comma decimal. Empty/unparseable → null (never NaN, never a
|
|
77
|
+
* silent 0 — empty and 0 stay distinct).
|
|
78
|
+
* @param {string} text
|
|
79
|
+
* @returns {number | null}
|
|
80
|
+
*/
|
|
81
|
+
function parseAmount(text) {
|
|
82
|
+
const trimmed = text.trim().replace(/\s+/g, '');
|
|
83
|
+
if (!trimmed) return null;
|
|
84
|
+
const normalized =
|
|
85
|
+
trimmed.includes(',') && !trimmed.includes('.')
|
|
86
|
+
? trimmed.replace(',', '.')
|
|
87
|
+
: trimmed;
|
|
88
|
+
const parsed = Number(normalized);
|
|
89
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function handleFocus() {
|
|
93
|
+
if (readonly || disabled) return;
|
|
94
|
+
draft = value === null || value === undefined ? '' : String(value);
|
|
95
|
+
focused = true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** @param {Event} event */
|
|
99
|
+
function handleInput(event) {
|
|
100
|
+
draft = /** @type {HTMLInputElement} */ (event.currentTarget).value;
|
|
101
|
+
const parsed = parseAmount(draft);
|
|
102
|
+
value = parsed;
|
|
103
|
+
onchange?.(parsed);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function handleBlur() {
|
|
107
|
+
focused = false;
|
|
108
|
+
}
|
|
109
|
+
</script>
|
|
110
|
+
|
|
111
|
+
<div class="money-input input-group {className}">
|
|
112
|
+
{#if label}
|
|
113
|
+
<label class="input-label" for={inputId}>{label}</label>
|
|
114
|
+
{/if}
|
|
115
|
+
|
|
116
|
+
<input
|
|
117
|
+
id={inputId}
|
|
118
|
+
{name}
|
|
119
|
+
type="text"
|
|
120
|
+
inputmode="decimal"
|
|
121
|
+
class="input input-{size}"
|
|
122
|
+
class:input-error={!!error}
|
|
123
|
+
class:input-readonly={readonly}
|
|
124
|
+
aria-invalid={error ? true : undefined}
|
|
125
|
+
aria-describedby={hasHint ? hintId : undefined}
|
|
126
|
+
{placeholder}
|
|
127
|
+
{disabled}
|
|
128
|
+
{readonly}
|
|
129
|
+
value={display}
|
|
130
|
+
onfocus={handleFocus}
|
|
131
|
+
oninput={handleInput}
|
|
132
|
+
onblur={handleBlur}
|
|
133
|
+
autocomplete="off"
|
|
134
|
+
{...rest}
|
|
135
|
+
/>
|
|
136
|
+
|
|
137
|
+
{#if error}
|
|
138
|
+
<span id={hintId} class="input-error-text" role="alert">{error}</span>
|
|
139
|
+
{:else if help}
|
|
140
|
+
<span id={hintId} class="input-help">{help}</span>
|
|
141
|
+
{/if}
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<style>
|
|
145
|
+
.money-input {
|
|
146
|
+
display: flex;
|
|
147
|
+
flex-direction: column;
|
|
148
|
+
gap: var(--input-label-gap);
|
|
149
|
+
width: 100%;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.input-label {
|
|
153
|
+
font-family: var(--input-label-font);
|
|
154
|
+
font-size: var(--input-label-size);
|
|
155
|
+
letter-spacing: var(--input-label-tracking);
|
|
156
|
+
color: var(--input-label-color);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.input {
|
|
160
|
+
font-family: var(--input-font);
|
|
161
|
+
font-size: var(--input-font-size);
|
|
162
|
+
border: var(--input-border);
|
|
163
|
+
border-radius: var(--input-radius);
|
|
164
|
+
background: var(--input-bg);
|
|
165
|
+
color: var(--input-text);
|
|
166
|
+
transition: border var(--input-transition);
|
|
167
|
+
width: 100%;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.input-sm {
|
|
171
|
+
height: var(--input-sm-height);
|
|
172
|
+
padding: 0 var(--input-sm-padding-x);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.input-md {
|
|
176
|
+
height: var(--input-md-height);
|
|
177
|
+
padding: 0 var(--input-md-padding-x);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.input-lg {
|
|
181
|
+
height: var(--input-lg-height);
|
|
182
|
+
padding: 0 var(--input-lg-padding-x);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.input::placeholder {
|
|
186
|
+
color: var(--input-placeholder);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.input:focus {
|
|
190
|
+
outline: none;
|
|
191
|
+
border: var(--input-border-focus);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.input:disabled {
|
|
195
|
+
opacity: 0.5;
|
|
196
|
+
cursor: not-allowed;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.input-readonly {
|
|
200
|
+
background: var(--color-surface-secondary);
|
|
201
|
+
cursor: default;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.input-error {
|
|
205
|
+
border-color: var(--input-error-border-color);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.input-help {
|
|
209
|
+
font-family: var(--input-help-font);
|
|
210
|
+
font-size: var(--input-help-size);
|
|
211
|
+
color: var(--input-help-color);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.input-error-text {
|
|
215
|
+
font-family: var(--input-help-font);
|
|
216
|
+
font-size: var(--input-help-size);
|
|
217
|
+
color: var(--input-error-text);
|
|
218
|
+
}
|
|
219
|
+
</style>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export default MoneyInput;
|
|
2
|
+
type MoneyInput = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* MoneyInput
|
|
8
|
+
*
|
|
9
|
+
* Number-semantics input with a currency affordance (#35, atelier#669 V1).
|
|
10
|
+
* The WIRE value is always a raw number (or null when empty — empty and 0
|
|
11
|
+
* are distinct); locale-aware currency formatting appears while the field is
|
|
12
|
+
* NOT being edited; focus switches to the raw editable number.
|
|
13
|
+
* Consumes --input-* tokens (mirrors Input's field chrome).
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <MoneyInput label="BUDGET" value={1234.5} onchange={(n) => save(n)} />
|
|
17
|
+
*
|
|
18
|
+
* @example Non-default currency
|
|
19
|
+
* <MoneyInput label="FEE" currency="USD" locale="en-US" />
|
|
20
|
+
*/
|
|
21
|
+
declare const MoneyInput: import("svelte").Component<{
|
|
22
|
+
value?: any;
|
|
23
|
+
currency?: string;
|
|
24
|
+
locale?: any;
|
|
25
|
+
label?: any;
|
|
26
|
+
placeholder?: any;
|
|
27
|
+
help?: any;
|
|
28
|
+
error?: any;
|
|
29
|
+
size?: string;
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
readonly?: boolean;
|
|
32
|
+
id?: any;
|
|
33
|
+
name?: any;
|
|
34
|
+
onchange?: any;
|
|
35
|
+
class?: string;
|
|
36
|
+
} & Record<string, any>, {}, "">;
|
|
37
|
+
type $$ComponentProps = {
|
|
38
|
+
value?: any;
|
|
39
|
+
currency?: string;
|
|
40
|
+
locale?: any;
|
|
41
|
+
label?: any;
|
|
42
|
+
placeholder?: any;
|
|
43
|
+
help?: any;
|
|
44
|
+
error?: any;
|
|
45
|
+
size?: string;
|
|
46
|
+
disabled?: boolean;
|
|
47
|
+
readonly?: boolean;
|
|
48
|
+
id?: any;
|
|
49
|
+
name?: any;
|
|
50
|
+
onchange?: any;
|
|
51
|
+
class?: string;
|
|
52
|
+
} & Record<string, any>;
|