@marianmeres/stuic 3.152.0 → 3.154.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 +3 -0
- package/API.md +1 -0
- package/README.md +72 -0
- package/dist/components/FieldsBuilder/FieldsBuilder.svelte +1214 -0
- package/dist/components/FieldsBuilder/FieldsBuilder.svelte.d.ts +102 -0
- package/dist/components/FieldsBuilder/README.md +248 -0
- package/dist/components/FieldsBuilder/_internal/LocalizedTextInput.svelte +229 -0
- package/dist/components/FieldsBuilder/_internal/LocalizedTextInput.svelte.d.ts +30 -0
- package/dist/components/FieldsBuilder/_internal/OptionsEditor.svelte +296 -0
- package/dist/components/FieldsBuilder/_internal/OptionsEditor.svelte.d.ts +18 -0
- package/dist/components/FieldsBuilder/i18n-sk.d.ts +22 -0
- package/dist/components/FieldsBuilder/i18n-sk.js +81 -0
- package/dist/components/FieldsBuilder/i18n.d.ts +84 -0
- package/dist/components/FieldsBuilder/i18n.js +90 -0
- package/dist/components/FieldsBuilder/index.css +187 -0
- package/dist/components/FieldsBuilder/index.d.ts +5 -0
- package/dist/components/FieldsBuilder/index.js +4 -0
- package/dist/components/FieldsBuilder/types.d.ts +76 -0
- package/dist/components/FieldsBuilder/types.js +1 -0
- package/dist/components/FieldsBuilder/utils.d.ts +66 -0
- package/dist/components/FieldsBuilder/utils.js +153 -0
- package/dist/css/frame.css +109 -0
- package/dist/index.css +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/docs/RATIO_LOCKED_FRAME.md +466 -0
- package/docs/architecture.md +6 -0
- package/docs/domains/components.md +61 -1
- package/docs/domains/css-presets.md +304 -0
- package/docs/domains/theming.md +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
INTERNAL to FieldsBuilder — not exported from the package.
|
|
3
|
+
|
|
4
|
+
Editor for a choice-type field's option list (`FieldOptionDef[]`): add,
|
|
5
|
+
remove, reorder (buttons), localized labels, and auto-derived machine
|
|
6
|
+
values. Note this deliberately does NOT reuse `FieldOptions` — that
|
|
7
|
+
component is a select-from-a-list picker, not an option-list author.
|
|
8
|
+
|
|
9
|
+
Value derivation mirrors the field-key policy: while an option's value is
|
|
10
|
+
untouched, it is derived from the label (slugified, unique among sibling
|
|
11
|
+
values); the first manual edit stops derivation for that option. Options
|
|
12
|
+
present at mount never re-derive (their value may already identify stored
|
|
13
|
+
data).
|
|
14
|
+
|
|
15
|
+
Each option carries internal meta ({ oid, touched }) kept in lockstep with
|
|
16
|
+
the bound `options` array. The #each is keyed by `oid` so reordering MOVES
|
|
17
|
+
the DOM rows — focus travels with the row — and a focus-follow pass (same
|
|
18
|
+
pattern as the parent's focusRowButton) covers the disabled-at-boundary
|
|
19
|
+
case. Moves/removals are announced via a local polite live region.
|
|
20
|
+
-->
|
|
21
|
+
<script lang="ts">
|
|
22
|
+
import { tick } from "svelte";
|
|
23
|
+
import { tooltip } from "../../../actions/index.js";
|
|
24
|
+
import {
|
|
25
|
+
iconArrowDown,
|
|
26
|
+
iconArrowUp,
|
|
27
|
+
iconPlus,
|
|
28
|
+
iconTrash,
|
|
29
|
+
} from "../../../icons/index.js";
|
|
30
|
+
import { getId } from "../../../utils/get-id.js";
|
|
31
|
+
import { twMerge } from "../../../utils/tw-merge.js";
|
|
32
|
+
import type { TranslateFn } from "../../../types.js";
|
|
33
|
+
import type { FieldOptionDef } from "../types.js";
|
|
34
|
+
import { getLocalizedText, slugifyKey, uniqueKey } from "../utils.js";
|
|
35
|
+
import LocalizedTextInput from "./LocalizedTextInput.svelte";
|
|
36
|
+
|
|
37
|
+
interface Props {
|
|
38
|
+
options?: FieldOptionDef[];
|
|
39
|
+
languages?: string[];
|
|
40
|
+
defaultLanguage?: string;
|
|
41
|
+
languageLabels?: Record<string, string>;
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
/** `lock.options` — render the list read-only. */
|
|
44
|
+
locked?: boolean;
|
|
45
|
+
tabindex?: number;
|
|
46
|
+
t?: TranslateFn;
|
|
47
|
+
/** Fired after any change to `options`. */
|
|
48
|
+
onChange?: () => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let {
|
|
52
|
+
options = $bindable(),
|
|
53
|
+
languages,
|
|
54
|
+
defaultLanguage,
|
|
55
|
+
languageLabels,
|
|
56
|
+
disabled = false,
|
|
57
|
+
locked = false,
|
|
58
|
+
tabindex = 0,
|
|
59
|
+
t = (k: string) => k,
|
|
60
|
+
onChange,
|
|
61
|
+
}: Props = $props();
|
|
62
|
+
|
|
63
|
+
const _defaultLanguage = $derived(defaultLanguage || languages?.[0]);
|
|
64
|
+
|
|
65
|
+
interface OptionMeta {
|
|
66
|
+
/** Stable render id (options themselves have no identity). */
|
|
67
|
+
oid: string;
|
|
68
|
+
/** Value manually edited — derivation stops. Mount-time options: true. */
|
|
69
|
+
touched: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// index-aligned with `options`; every mutation below keeps them in lockstep
|
|
73
|
+
let meta: OptionMeta[] = $state(
|
|
74
|
+
(options ?? []).map(() => ({ oid: getId(), touched: true }))
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
let rootEl: HTMLElement | undefined = $state();
|
|
78
|
+
let labelEditors: Record<string, LocalizedTextInput | undefined> = $state({});
|
|
79
|
+
let liveAnnouncement = $state("");
|
|
80
|
+
|
|
81
|
+
const _readonly = $derived(disabled || locked);
|
|
82
|
+
|
|
83
|
+
function sync() {
|
|
84
|
+
onChange?.();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// clear-then-set so repeated identical announcements still get read out
|
|
88
|
+
function announce(
|
|
89
|
+
key: string,
|
|
90
|
+
option: FieldOptionDef,
|
|
91
|
+
values?: Record<string, number>
|
|
92
|
+
) {
|
|
93
|
+
liveAnnouncement = "";
|
|
94
|
+
const msg = String(
|
|
95
|
+
t(key, { label: getLocalizedText(option.label, _defaultLanguage), ...values })
|
|
96
|
+
);
|
|
97
|
+
tick().then(() => (liveAnnouncement = msg));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function addOption() {
|
|
101
|
+
const m: OptionMeta = { oid: getId(), touched: false };
|
|
102
|
+
options = [...(options ?? []), { value: "", label: "" }];
|
|
103
|
+
meta = [...meta, m];
|
|
104
|
+
sync();
|
|
105
|
+
tick().then(() => labelEditors[m.oid]?.focus?.());
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function removeOption(idx: number) {
|
|
109
|
+
const option = (options ?? [])[idx];
|
|
110
|
+
options = (options ?? []).filter((_, i) => i !== idx);
|
|
111
|
+
meta = meta.filter((_, i) => i !== idx);
|
|
112
|
+
sync();
|
|
113
|
+
if (option) announce("removed_option", option);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function moveOption(from: number, to: number) {
|
|
117
|
+
const list = options ?? [];
|
|
118
|
+
if (to < 0 || to >= list.length || from === to) return;
|
|
119
|
+
const nextOptions = [...list];
|
|
120
|
+
const [movedOption] = nextOptions.splice(from, 1);
|
|
121
|
+
nextOptions.splice(to, 0, movedOption);
|
|
122
|
+
options = nextOptions;
|
|
123
|
+
const nextMeta = [...meta];
|
|
124
|
+
const [movedMeta] = nextMeta.splice(from, 1);
|
|
125
|
+
nextMeta.splice(to, 0, movedMeta);
|
|
126
|
+
meta = nextMeta;
|
|
127
|
+
sync();
|
|
128
|
+
announce(to < from ? "moved_up" : "moved_down", movedOption, {
|
|
129
|
+
position: to + 1,
|
|
130
|
+
total: nextOptions.length,
|
|
131
|
+
});
|
|
132
|
+
focusOptionButton(movedMeta.oid, to < from ? "up" : "down");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// same pattern as the parent's focusRowButton: re-focus the pressed logical
|
|
136
|
+
// button on the moved row; at a boundary fall back to any enabled one
|
|
137
|
+
function focusOptionButton(oid: string, which: "up" | "down") {
|
|
138
|
+
tick().then(() => {
|
|
139
|
+
const rowEl = rootEl?.querySelector<HTMLElement>(`[data-opt-row="${oid}"]`);
|
|
140
|
+
if (!rowEl) return;
|
|
141
|
+
let btn = rowEl.querySelector<HTMLButtonElement>(`[data-opt-btn="${which}"]`);
|
|
142
|
+
if (!btn || btn.disabled) {
|
|
143
|
+
btn =
|
|
144
|
+
rowEl.querySelector<HTMLButtonElement>(`[data-opt-btn="up"]:not([disabled])`) ||
|
|
145
|
+
rowEl.querySelector<HTMLButtonElement>(`[data-opt-btn="down"]:not([disabled])`);
|
|
146
|
+
}
|
|
147
|
+
btn?.focus();
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function onLabelInput(idx: number) {
|
|
152
|
+
const list = options ?? [];
|
|
153
|
+
const option = list[idx];
|
|
154
|
+
if (!option || meta[idx]?.touched) return sync();
|
|
155
|
+
const base = slugifyKey(getLocalizedText(option.label, _defaultLanguage));
|
|
156
|
+
option.value = base
|
|
157
|
+
? uniqueKey(base, (v) => list.some((o, i) => i !== idx && o.value === v))
|
|
158
|
+
: "";
|
|
159
|
+
sync();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function onValueInput(idx: number, newValue: string) {
|
|
163
|
+
const option = (options ?? [])[idx];
|
|
164
|
+
if (!option) return;
|
|
165
|
+
if (meta[idx]) meta[idx].touched = true;
|
|
166
|
+
option.value = newValue;
|
|
167
|
+
sync();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const INPUT_CLS = [
|
|
171
|
+
"rounded bg-(--stuic-color-input)",
|
|
172
|
+
"border border-(--stuic-color-border)",
|
|
173
|
+
"focus:border-(--stuic-color-border-hover)",
|
|
174
|
+
"focus:outline-none focus:ring-0",
|
|
175
|
+
"focus-visible:outline-none focus-visible:ring-0",
|
|
176
|
+
].join(" ");
|
|
177
|
+
|
|
178
|
+
const VALUE_INPUT_CLS = twMerge(INPUT_CLS, "font-mono text-sm");
|
|
179
|
+
|
|
180
|
+
const BTN_CLS = [
|
|
181
|
+
"p-1 rounded",
|
|
182
|
+
"opacity-50 hover:opacity-100",
|
|
183
|
+
"hover:bg-(--stuic-color-muted)",
|
|
184
|
+
"focus-visible:outline-(--stuic-color-border-hover)",
|
|
185
|
+
"disabled:opacity-25 disabled:cursor-not-allowed disabled:hover:bg-transparent",
|
|
186
|
+
].join(" ");
|
|
187
|
+
</script>
|
|
188
|
+
|
|
189
|
+
<div class="fb-options w-full" bind:this={rootEl}>
|
|
190
|
+
{#if locked && !(options ?? []).length}
|
|
191
|
+
<div class="fb-hint text-sm py-1">{t("empty_options_message")}</div>
|
|
192
|
+
{/if}
|
|
193
|
+
|
|
194
|
+
{#each options ?? [] as option, idx (meta[idx]?.oid ?? idx)}
|
|
195
|
+
<div
|
|
196
|
+
class={twMerge(
|
|
197
|
+
"fb-option-row flex items-center gap-1.5 py-1",
|
|
198
|
+
idx > 0 && "fb-option-divider"
|
|
199
|
+
)}
|
|
200
|
+
data-opt-row={meta[idx]?.oid}
|
|
201
|
+
>
|
|
202
|
+
{#if !locked}
|
|
203
|
+
<div class="flex flex-col shrink-0">
|
|
204
|
+
<button
|
|
205
|
+
type="button"
|
|
206
|
+
class={twMerge(BTN_CLS, "p-0.5")}
|
|
207
|
+
data-opt-btn="up"
|
|
208
|
+
onclick={() => moveOption(idx, idx - 1)}
|
|
209
|
+
disabled={_readonly || idx === 0}
|
|
210
|
+
aria-label={String(t("move_up"))}
|
|
211
|
+
>
|
|
212
|
+
{@html iconArrowUp({ size: 12 })}
|
|
213
|
+
</button>
|
|
214
|
+
<button
|
|
215
|
+
type="button"
|
|
216
|
+
class={twMerge(BTN_CLS, "p-0.5")}
|
|
217
|
+
data-opt-btn="down"
|
|
218
|
+
onclick={() => moveOption(idx, idx + 1)}
|
|
219
|
+
disabled={_readonly || idx === (options?.length ?? 0) - 1}
|
|
220
|
+
aria-label={String(t("move_down"))}
|
|
221
|
+
>
|
|
222
|
+
{@html iconArrowDown({ size: 12 })}
|
|
223
|
+
</button>
|
|
224
|
+
</div>
|
|
225
|
+
{/if}
|
|
226
|
+
|
|
227
|
+
<div class="flex-1 min-w-0">
|
|
228
|
+
<!-- locked -> readonly (NOT disabled): the labels and their
|
|
229
|
+
translations must stay reachable and readable -->
|
|
230
|
+
<LocalizedTextInput
|
|
231
|
+
bind:value={option.label}
|
|
232
|
+
bind:this={labelEditors[meta[idx]?.oid ?? String(idx)]}
|
|
233
|
+
{languages}
|
|
234
|
+
{defaultLanguage}
|
|
235
|
+
{languageLabels}
|
|
236
|
+
{disabled}
|
|
237
|
+
readonly={locked}
|
|
238
|
+
{tabindex}
|
|
239
|
+
{t}
|
|
240
|
+
class={INPUT_CLS}
|
|
241
|
+
placeholder={String(t("option_label_placeholder"))}
|
|
242
|
+
ariaLabel={String(t("option_label_placeholder"))}
|
|
243
|
+
onInput={() => onLabelInput(idx)}
|
|
244
|
+
/>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
<input
|
|
248
|
+
type="text"
|
|
249
|
+
value={option.value}
|
|
250
|
+
oninput={(e) => onValueInput(idx, e.currentTarget.value)}
|
|
251
|
+
class={twMerge(VALUE_INPUT_CLS, "fb-option-value w-28 sm:w-36 shrink-0")}
|
|
252
|
+
readonly={locked}
|
|
253
|
+
{disabled}
|
|
254
|
+
{tabindex}
|
|
255
|
+
placeholder={String(t("option_value_placeholder"))}
|
|
256
|
+
aria-label={String(t("option_value_placeholder"))}
|
|
257
|
+
use:tooltip={() => ({ enabled: !_readonly, content: t("option_value_hint") })}
|
|
258
|
+
/>
|
|
259
|
+
|
|
260
|
+
{#if !locked}
|
|
261
|
+
<button
|
|
262
|
+
type="button"
|
|
263
|
+
class={twMerge(BTN_CLS, "shrink-0")}
|
|
264
|
+
onclick={() => removeOption(idx)}
|
|
265
|
+
disabled={_readonly}
|
|
266
|
+
aria-label={String(t("remove_option"))}
|
|
267
|
+
use:tooltip={() => ({ enabled: !_readonly, content: t("remove_option") })}
|
|
268
|
+
>
|
|
269
|
+
{@html iconTrash({ size: 14 })}
|
|
270
|
+
</button>
|
|
271
|
+
{/if}
|
|
272
|
+
</div>
|
|
273
|
+
{/each}
|
|
274
|
+
|
|
275
|
+
{#if !locked}
|
|
276
|
+
<button
|
|
277
|
+
type="button"
|
|
278
|
+
onclick={addOption}
|
|
279
|
+
class={twMerge(
|
|
280
|
+
"fb-add-option-btn",
|
|
281
|
+
"flex items-center gap-1 text-sm opacity-75 hover:opacity-100",
|
|
282
|
+
"bg-(--stuic-color-muted)",
|
|
283
|
+
"p-1 pr-2 rounded hover:bg-(--stuic-color-muted-hover)",
|
|
284
|
+
"disabled:opacity-25 disabled:cursor-not-allowed",
|
|
285
|
+
(options ?? []).length > 0 && "mt-1"
|
|
286
|
+
)}
|
|
287
|
+
{disabled}
|
|
288
|
+
{tabindex}
|
|
289
|
+
>
|
|
290
|
+
{@html iconPlus({ size: 14 })}
|
|
291
|
+
<span>{t("add_option")}</span>
|
|
292
|
+
</button>
|
|
293
|
+
{/if}
|
|
294
|
+
|
|
295
|
+
<div class="sr-only" aria-live="polite">{liveAnnouncement}</div>
|
|
296
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TranslateFn } from "../../../types.js";
|
|
2
|
+
import type { FieldOptionDef } from "../types.js";
|
|
3
|
+
interface Props {
|
|
4
|
+
options?: FieldOptionDef[];
|
|
5
|
+
languages?: string[];
|
|
6
|
+
defaultLanguage?: string;
|
|
7
|
+
languageLabels?: Record<string, string>;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
/** `lock.options` — render the list read-only. */
|
|
10
|
+
locked?: boolean;
|
|
11
|
+
tabindex?: number;
|
|
12
|
+
t?: TranslateFn;
|
|
13
|
+
/** Fired after any change to `options`. */
|
|
14
|
+
onChange?: () => void;
|
|
15
|
+
}
|
|
16
|
+
declare const OptionsEditor: import("svelte").Component<Props, {}, "options">;
|
|
17
|
+
type OptionsEditor = ReturnType<typeof OptionsEditor>;
|
|
18
|
+
export default OptionsEditor;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { FieldTypeDef } from "./types.js";
|
|
2
|
+
import type { FieldsBuilderMessages } from "./i18n.js";
|
|
3
|
+
/**
|
|
4
|
+
* Slovak message catalog for `FieldsBuilder`. Opt-in — English stays the
|
|
5
|
+
* built-in default, and this module is only pulled into a bundle when it is
|
|
6
|
+
* actually imported (the component itself never references it).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```svelte
|
|
10
|
+
* <script>
|
|
11
|
+
* import { createFieldsBuilderT, FIELDS_BUILDER_MESSAGES_SK } from "@marianmeres/stuic";
|
|
12
|
+
* const t = createFieldsBuilderT(FIELDS_BUILDER_MESSAGES_SK);
|
|
13
|
+
* </script>
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare const FIELDS_BUILDER_MESSAGES_SK: FieldsBuilderMessages;
|
|
17
|
+
/**
|
|
18
|
+
* Slovak counterpart of `FIELDS_BUILDER_DEFAULT_TYPES` — the `types` palette is
|
|
19
|
+
* consumer-owned data (and a required prop), so translating the messages alone
|
|
20
|
+
* would still leave the type select in English.
|
|
21
|
+
*/
|
|
22
|
+
export declare const FIELDS_BUILDER_DEFAULT_TYPES_SK: FieldTypeDef[];
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slovak message catalog for `FieldsBuilder`. Opt-in — English stays the
|
|
3
|
+
* built-in default, and this module is only pulled into a bundle when it is
|
|
4
|
+
* actually imported (the component itself never references it).
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <script>
|
|
9
|
+
* import { createFieldsBuilderT, FIELDS_BUILDER_MESSAGES_SK } from "@marianmeres/stuic";
|
|
10
|
+
* const t = createFieldsBuilderT(FIELDS_BUILDER_MESSAGES_SK);
|
|
11
|
+
* </script>
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export const FIELDS_BUILDER_MESSAGES_SK = {
|
|
15
|
+
field_req_att: "Toto pole vyžaduje pozornosť. Skontrolujte ho a skúste to znova.",
|
|
16
|
+
at_least_one_field: "Vyžaduje sa aspoň jedno pole",
|
|
17
|
+
add_label: "Pridať pole",
|
|
18
|
+
empty_message: "Zatiaľ žiadne polia",
|
|
19
|
+
empty_options_message: "Nie sú definované žiadne možnosti",
|
|
20
|
+
untitled: "Nepomenované pole",
|
|
21
|
+
deleted: "Zmazané",
|
|
22
|
+
delete_field: "Zmazať pole",
|
|
23
|
+
undo_delete: "Späť",
|
|
24
|
+
removed_field: "Zmazané pole: {{label}}",
|
|
25
|
+
restored_field: "Obnovené pole: {{label}}",
|
|
26
|
+
move_up: "Posunúť vyššie",
|
|
27
|
+
move_down: "Posunúť nižšie",
|
|
28
|
+
moved_up: "Posunuté vyššie: {{label}} ({{position}} z {{total}})",
|
|
29
|
+
moved_down: "Posunuté nižšie: {{label}} ({{position}} z {{total}})",
|
|
30
|
+
row_has_errors: "Obsahuje chyby",
|
|
31
|
+
drag_to_reorder: "Potiahnutím zmeníte poradie",
|
|
32
|
+
label_label: "Názov",
|
|
33
|
+
label_placeholder: "Názov poľa",
|
|
34
|
+
description_label: "Popis",
|
|
35
|
+
type_label: "Typ",
|
|
36
|
+
required_label: "Povinné",
|
|
37
|
+
options_label: "Možnosti",
|
|
38
|
+
add_option: "Pridať možnosť",
|
|
39
|
+
option_label_placeholder: "Názov možnosti",
|
|
40
|
+
option_value_placeholder: "hodnota",
|
|
41
|
+
option_value_hint: "Uložená strojová hodnota tejto možnosti",
|
|
42
|
+
remove_option: "Odstrániť možnosť",
|
|
43
|
+
removed_option: "Odstránená možnosť: {{label}}",
|
|
44
|
+
advanced_label: "Rozšírené",
|
|
45
|
+
key_label: "Kľúč",
|
|
46
|
+
key_hint: "Jedinečný strojový identifikátor tohto poľa. Vypĺňa sa automaticky z názvu.",
|
|
47
|
+
key_locked_hint: "Kľúč identifikuje uložené údaje a už sa nedá zmeniť.",
|
|
48
|
+
show_translations: "Zobraziť preklady",
|
|
49
|
+
hide_translations: "Skryť preklady",
|
|
50
|
+
type_change_warning: "Zmena typu existujúceho poľa nemusí sedieť s údajmi, ktoré sú pod ním už uložené.",
|
|
51
|
+
unknown_type_warning: "Toto pole má typ, ktorý tento editor nepozná. Zostáva nezmenené a nedá sa tu upravovať.",
|
|
52
|
+
preview_label: "Náhľad",
|
|
53
|
+
err_label_required: "Názov je povinný",
|
|
54
|
+
err_key_required: "Kľúč je povinný",
|
|
55
|
+
err_key_pattern: "Kľúč musí začínať malým písmenom a môže obsahovať iba malé písmená, číslice a podčiarkovníky",
|
|
56
|
+
err_key_maxlength: "Kľúč je príliš dlhý (max. {{max}} znakov)",
|
|
57
|
+
err_key_duplicate: "Tento kľúč už používa iné pole",
|
|
58
|
+
err_key_reserved: "Tento kľúč je rezervovaný a nedá sa použiť",
|
|
59
|
+
err_options_required: "Pridajte aspoň jednu možnosť",
|
|
60
|
+
err_option_value_required: "Každá možnosť musí mať hodnotu",
|
|
61
|
+
err_option_value_duplicate: "Hodnoty možností musia byť jedinečné",
|
|
62
|
+
err_max_fields: "Maximálny počet polí je {{max}}",
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Slovak counterpart of `FIELDS_BUILDER_DEFAULT_TYPES` — the `types` palette is
|
|
66
|
+
* consumer-owned data (and a required prop), so translating the messages alone
|
|
67
|
+
* would still leave the type select in English.
|
|
68
|
+
*/
|
|
69
|
+
export const FIELDS_BUILDER_DEFAULT_TYPES_SK = [
|
|
70
|
+
{ type: "text", label: "Text", description: "Jeden riadok textu" },
|
|
71
|
+
{ type: "longtext", label: "Dlhý text", description: "Viac riadkov textu" },
|
|
72
|
+
{ type: "number", label: "Číslo", description: "Číselná hodnota" },
|
|
73
|
+
{ type: "checkbox", label: "Áno / nie", description: "Jedno zaškrtávacie políčko" },
|
|
74
|
+
{
|
|
75
|
+
type: "select",
|
|
76
|
+
label: "Výber",
|
|
77
|
+
description: "Výber jednej z ponúkaných možností",
|
|
78
|
+
supportsOptions: true,
|
|
79
|
+
},
|
|
80
|
+
{ type: "date", label: "Dátum", description: "Kalendárny dátum" },
|
|
81
|
+
];
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { TranslateFn } from "../../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The built-in (English) message catalog of `FieldsBuilder`. Also the fallback
|
|
4
|
+
* of every other bundled locale, so a locale missing a key still renders text.
|
|
5
|
+
*
|
|
6
|
+
* Placeholders are mustache-style (`{{label}}`, `{{max}}`, ...).
|
|
7
|
+
*/
|
|
8
|
+
export declare const FIELDS_BUILDER_MESSAGES_EN: {
|
|
9
|
+
field_req_att: string;
|
|
10
|
+
at_least_one_field: string;
|
|
11
|
+
add_label: string;
|
|
12
|
+
empty_message: string;
|
|
13
|
+
empty_options_message: string;
|
|
14
|
+
untitled: string;
|
|
15
|
+
deleted: string;
|
|
16
|
+
delete_field: string;
|
|
17
|
+
undo_delete: string;
|
|
18
|
+
removed_field: string;
|
|
19
|
+
restored_field: string;
|
|
20
|
+
move_up: string;
|
|
21
|
+
move_down: string;
|
|
22
|
+
moved_up: string;
|
|
23
|
+
moved_down: string;
|
|
24
|
+
row_has_errors: string;
|
|
25
|
+
drag_to_reorder: string;
|
|
26
|
+
label_label: string;
|
|
27
|
+
label_placeholder: string;
|
|
28
|
+
description_label: string;
|
|
29
|
+
type_label: string;
|
|
30
|
+
required_label: string;
|
|
31
|
+
options_label: string;
|
|
32
|
+
add_option: string;
|
|
33
|
+
option_label_placeholder: string;
|
|
34
|
+
option_value_placeholder: string;
|
|
35
|
+
option_value_hint: string;
|
|
36
|
+
remove_option: string;
|
|
37
|
+
removed_option: string;
|
|
38
|
+
advanced_label: string;
|
|
39
|
+
key_label: string;
|
|
40
|
+
key_hint: string;
|
|
41
|
+
key_locked_hint: string;
|
|
42
|
+
show_translations: string;
|
|
43
|
+
hide_translations: string;
|
|
44
|
+
type_change_warning: string;
|
|
45
|
+
unknown_type_warning: string;
|
|
46
|
+
preview_label: string;
|
|
47
|
+
err_label_required: string;
|
|
48
|
+
err_key_required: string;
|
|
49
|
+
err_key_pattern: string;
|
|
50
|
+
err_key_maxlength: string;
|
|
51
|
+
err_key_duplicate: string;
|
|
52
|
+
err_key_reserved: string;
|
|
53
|
+
err_options_required: string;
|
|
54
|
+
err_option_value_required: string;
|
|
55
|
+
err_option_value_duplicate: string;
|
|
56
|
+
err_max_fields: string;
|
|
57
|
+
};
|
|
58
|
+
/** Every message key `FieldsBuilder` (and its internals) may look up. */
|
|
59
|
+
export type FieldsBuilderMessageKey = keyof typeof FIELDS_BUILDER_MESSAGES_EN;
|
|
60
|
+
/** A (possibly partial) catalog for one locale. */
|
|
61
|
+
export type FieldsBuilderMessages = Record<FieldsBuilderMessageKey, string>;
|
|
62
|
+
/**
|
|
63
|
+
* Builds the `t` prop of `FieldsBuilder` from a message catalog. Unknown or
|
|
64
|
+
* untranslated keys fall back to `fallbackMessages` (English by default), so a
|
|
65
|
+
* catalog may safely be partial and never renders a raw key.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```svelte
|
|
69
|
+
* <script>
|
|
70
|
+
* import {
|
|
71
|
+
* FieldsBuilder,
|
|
72
|
+
* createFieldsBuilderT,
|
|
73
|
+
* FIELDS_BUILDER_MESSAGES_SK,
|
|
74
|
+
* FIELDS_BUILDER_DEFAULT_TYPES_SK,
|
|
75
|
+
* } from "@marianmeres/stuic";
|
|
76
|
+
* const t = createFieldsBuilderT(FIELDS_BUILDER_MESSAGES_SK);
|
|
77
|
+
* </script>
|
|
78
|
+
*
|
|
79
|
+
* <FieldsBuilder bind:value name="fields" types={FIELDS_BUILDER_DEFAULT_TYPES_SK} {t} />
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export declare function createFieldsBuilderT(messages: Partial<FieldsBuilderMessages> | Record<string, string>, fallbackMessages?: Partial<FieldsBuilderMessages> | Record<string, string>): TranslateFn;
|
|
83
|
+
/** The component's built-in English `t`. */
|
|
84
|
+
export declare const t_default: TranslateFn;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { isPlainObject } from "../../utils/is-plain-object.js";
|
|
2
|
+
import { replaceMap } from "../../utils/replace-map.js";
|
|
3
|
+
/**
|
|
4
|
+
* The built-in (English) message catalog of `FieldsBuilder`. Also the fallback
|
|
5
|
+
* of every other bundled locale, so a locale missing a key still renders text.
|
|
6
|
+
*
|
|
7
|
+
* Placeholders are mustache-style (`{{label}}`, `{{max}}`, ...).
|
|
8
|
+
*/
|
|
9
|
+
export const FIELDS_BUILDER_MESSAGES_EN = {
|
|
10
|
+
field_req_att: "This field requires attention. Please review and try again.",
|
|
11
|
+
at_least_one_field: "At least one field is required",
|
|
12
|
+
add_label: "Add field",
|
|
13
|
+
empty_message: "No fields yet",
|
|
14
|
+
empty_options_message: "No choices defined",
|
|
15
|
+
untitled: "Untitled field",
|
|
16
|
+
deleted: "Deleted",
|
|
17
|
+
delete_field: "Delete field",
|
|
18
|
+
undo_delete: "Undo",
|
|
19
|
+
removed_field: "Deleted field: {{label}}",
|
|
20
|
+
restored_field: "Restored field: {{label}}",
|
|
21
|
+
move_up: "Move up",
|
|
22
|
+
move_down: "Move down",
|
|
23
|
+
moved_up: "Moved up: {{label}} ({{position}} of {{total}})",
|
|
24
|
+
moved_down: "Moved down: {{label}} ({{position}} of {{total}})",
|
|
25
|
+
row_has_errors: "Has validation errors",
|
|
26
|
+
drag_to_reorder: "Drag to reorder",
|
|
27
|
+
label_label: "Label",
|
|
28
|
+
label_placeholder: "Field name",
|
|
29
|
+
description_label: "Description",
|
|
30
|
+
type_label: "Type",
|
|
31
|
+
required_label: "Required",
|
|
32
|
+
options_label: "Choices",
|
|
33
|
+
add_option: "Add choice",
|
|
34
|
+
option_label_placeholder: "Choice label",
|
|
35
|
+
option_value_placeholder: "value",
|
|
36
|
+
option_value_hint: "Stored machine value of this choice",
|
|
37
|
+
remove_option: "Remove choice",
|
|
38
|
+
removed_option: "Removed choice: {{label}}",
|
|
39
|
+
advanced_label: "Advanced",
|
|
40
|
+
key_label: "Key",
|
|
41
|
+
key_hint: "Unique machine identifier of this field. Filled in automatically from the label.",
|
|
42
|
+
key_locked_hint: "The key identifies stored data and can no longer be changed.",
|
|
43
|
+
show_translations: "Show translations",
|
|
44
|
+
hide_translations: "Hide translations",
|
|
45
|
+
type_change_warning: "Changing the type of an existing field may not fit data already stored under it.",
|
|
46
|
+
unknown_type_warning: "This field has a type this editor does not recognize. It is kept unchanged and cannot be edited here.",
|
|
47
|
+
preview_label: "Preview",
|
|
48
|
+
err_label_required: "Label is required",
|
|
49
|
+
err_key_required: "Key is required",
|
|
50
|
+
err_key_pattern: "Key must start with a lowercase letter and may contain only lowercase letters, numbers and underscores",
|
|
51
|
+
err_key_maxlength: "Key is too long (max {{max}} characters)",
|
|
52
|
+
err_key_duplicate: "This key is already used by another field",
|
|
53
|
+
err_key_reserved: "This key is reserved and cannot be used",
|
|
54
|
+
err_options_required: "Add at least one choice",
|
|
55
|
+
err_option_value_required: "Every choice needs a value",
|
|
56
|
+
err_option_value_duplicate: "Choice values must be unique",
|
|
57
|
+
err_max_fields: "Maximum number of fields is {{max}}",
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Builds the `t` prop of `FieldsBuilder` from a message catalog. Unknown or
|
|
61
|
+
* untranslated keys fall back to `fallbackMessages` (English by default), so a
|
|
62
|
+
* catalog may safely be partial and never renders a raw key.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```svelte
|
|
66
|
+
* <script>
|
|
67
|
+
* import {
|
|
68
|
+
* FieldsBuilder,
|
|
69
|
+
* createFieldsBuilderT,
|
|
70
|
+
* FIELDS_BUILDER_MESSAGES_SK,
|
|
71
|
+
* FIELDS_BUILDER_DEFAULT_TYPES_SK,
|
|
72
|
+
* } from "@marianmeres/stuic";
|
|
73
|
+
* const t = createFieldsBuilderT(FIELDS_BUILDER_MESSAGES_SK);
|
|
74
|
+
* </script>
|
|
75
|
+
*
|
|
76
|
+
* <FieldsBuilder bind:value name="fields" types={FIELDS_BUILDER_DEFAULT_TYPES_SK} {t} />
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export function createFieldsBuilderT(messages, fallbackMessages = FIELDS_BUILDER_MESSAGES_EN) {
|
|
80
|
+
return (k, values = null, fallback = "") => {
|
|
81
|
+
const out = messages[k] ??
|
|
82
|
+
fallbackMessages[k] ??
|
|
83
|
+
(typeof fallback === "string" ? fallback : k);
|
|
84
|
+
return isPlainObject(values)
|
|
85
|
+
? replaceMap(out, values)
|
|
86
|
+
: out;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** The component's built-in English `t`. */
|
|
90
|
+
export const t_default = createFieldsBuilderT(FIELDS_BUILDER_MESSAGES_EN);
|