@marianmeres/stuic 3.181.0 → 3.183.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/README.md +1 -1
- package/dist/components/FieldsBuilder/FieldsBuilder.svelte +101 -150
- package/dist/components/FieldsBuilder/FieldsBuilder.svelte.d.ts +10 -1
- package/dist/components/FieldsBuilder/README.md +82 -3
- package/dist/components/FieldsBuilder/_internal/ColumnsEditor.svelte +624 -0
- package/dist/components/FieldsBuilder/_internal/ColumnsEditor.svelte.d.ts +41 -0
- package/dist/components/FieldsBuilder/_internal/ExtrasEditor.svelte +174 -0
- package/dist/components/FieldsBuilder/_internal/ExtrasEditor.svelte.d.ts +20 -0
- package/dist/components/FieldsBuilder/_internal/column-meta.d.ts +27 -0
- package/dist/components/FieldsBuilder/_internal/column-meta.js +15 -0
- package/dist/components/FieldsBuilder/i18n-sk.js +17 -0
- package/dist/components/FieldsBuilder/i18n.d.ts +17 -0
- package/dist/components/FieldsBuilder/i18n.js +17 -0
- package/dist/components/FieldsBuilder/index.css +20 -0
- package/dist/components/FieldsBuilder/index.d.ts +2 -2
- package/dist/components/FieldsBuilder/types.d.ts +39 -0
- package/dist/components/FieldsBuilder/utils.d.ts +36 -1
- package/dist/components/FieldsBuilder/utils.js +180 -55
- package/dist/components/Input/FieldAssets.svelte +9 -132
- package/dist/components/Input/FieldSingleAsset.svelte +876 -0
- package/dist/components/Input/FieldSingleAsset.svelte.d.ts +135 -0
- package/dist/components/Input/FieldTable.svelte +907 -0
- package/dist/components/Input/FieldTable.svelte.d.ts +98 -0
- package/dist/components/Input/README.md +467 -20
- package/dist/components/Input/_internal/asset-helpers.d.ts +16 -0
- package/dist/components/Input/_internal/asset-helpers.js +46 -0
- package/dist/components/Input/_internal/paste-target.d.ts +15 -0
- package/dist/components/Input/_internal/paste-target.js +114 -0
- package/dist/components/Input/field-single-asset-i18n-sk.d.ts +21 -0
- package/dist/components/Input/field-single-asset-i18n-sk.js +49 -0
- package/dist/components/Input/field-single-asset-i18n.d.ts +68 -0
- package/dist/components/Input/field-single-asset-i18n.js +75 -0
- package/dist/components/Input/field-table-i18n-sk.d.ts +21 -0
- package/dist/components/Input/field-table-i18n-sk.js +41 -0
- package/dist/components/Input/field-table-i18n.d.ts +60 -0
- package/dist/components/Input/field-table-i18n.js +66 -0
- package/dist/components/Input/field-table-number.d.ts +48 -0
- package/dist/components/Input/field-table-number.js +86 -0
- package/dist/components/Input/index.css +801 -0
- package/dist/components/Input/index.d.ts +7 -0
- package/dist/components/Input/index.js +7 -0
- package/docs/domains/actions.md +3 -3
- package/docs/domains/components.md +45 -38
- package/package.json +1 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Does a file pass an `accept` attribute value? Shared by `FieldAssets` and
|
|
3
|
+
* `FieldSingleAsset`.
|
|
4
|
+
*
|
|
5
|
+
* Tokens are the HTML `accept` ones, comma separated: a MIME type
|
|
6
|
+
* (`image/png`), a wildcard MIME (`image/*`, `*`), or an extension (`.pdf`).
|
|
7
|
+
* MIME tokens are matched as prefixes of the file's `type`; extension tokens
|
|
8
|
+
* are matched against the end of the file's `name` (the browser's own picker
|
|
9
|
+
* filters by the same rule, so a drop and a pick agree). An empty `accept`
|
|
10
|
+
* accepts everything; so does a file with an unknown (empty) `type` when the
|
|
11
|
+
* token is a MIME one — we cannot tell, and the picker would have let it
|
|
12
|
+
* through too.
|
|
13
|
+
*/
|
|
14
|
+
export function isAcceptedType(allowedAccept, type, name) {
|
|
15
|
+
if (!allowedAccept)
|
|
16
|
+
return true;
|
|
17
|
+
const tokens = (allowedAccept ?? "")
|
|
18
|
+
.split(",")
|
|
19
|
+
.map((v) => `${v || ""}`.trim().toLowerCase())
|
|
20
|
+
.filter(Boolean);
|
|
21
|
+
if (!tokens.length)
|
|
22
|
+
return true;
|
|
23
|
+
const _type = `${type ?? ""}`.toLowerCase();
|
|
24
|
+
const _name = `${name ?? ""}`.toLowerCase();
|
|
25
|
+
return tokens.some((tok) => {
|
|
26
|
+
if (tok.startsWith("."))
|
|
27
|
+
return !!_name && _name.endsWith(tok);
|
|
28
|
+
// unknown type: cannot decide, let it through (legacy behavior)
|
|
29
|
+
if (!_type)
|
|
30
|
+
return true;
|
|
31
|
+
const prefix = tok.includes("*") ? tok.slice(0, tok.indexOf("*")) : tok;
|
|
32
|
+
return _type.startsWith(prefix);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/** `1234567` -> `"1.2 MB"` (1024-based, at most one decimal below 10). */
|
|
36
|
+
export function formatBytes(bytes) {
|
|
37
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
38
|
+
let v = Math.max(0, Number(bytes) || 0);
|
|
39
|
+
let i = 0;
|
|
40
|
+
while (v >= 1024 && i < units.length - 1) {
|
|
41
|
+
v /= 1024;
|
|
42
|
+
i++;
|
|
43
|
+
}
|
|
44
|
+
const rounded = i === 0 ? Math.round(v) : parseFloat(v.toFixed(v < 10 ? 1 : 0));
|
|
45
|
+
return `${rounded} ${units[i]}`;
|
|
46
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type PasteTarget = {
|
|
2
|
+
el: HTMLElement;
|
|
3
|
+
handle: (e: ClipboardEvent) => void;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Registers a pasteable field. Returns the unregister function. The document
|
|
7
|
+
* listener is installed with the first registration and removed with the last.
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerPasteTarget(t: PasteTarget): () => void;
|
|
10
|
+
/**
|
|
11
|
+
* The file entries of a paste. Prefers `items` (lets us keep only file-kind
|
|
12
|
+
* entries, e.g. a pasted screenshot); falls back to `.files` for browsers that
|
|
13
|
+
* only populate that. Empty for a plain-text paste.
|
|
14
|
+
*/
|
|
15
|
+
export declare function extractClipboardFiles(e: ClipboardEvent): File[];
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// --- Clipboard paste plumbing (see the `pasteable` prop of FieldAssets /
|
|
2
|
+
// FieldSingleAsset) ------------------------------------------------------------
|
|
3
|
+
// All mounted pasteable instances — of EITHER component — coordinate through ONE
|
|
4
|
+
// document-level `paste` listener (installed while at least one is mounted).
|
|
5
|
+
// Document-level (rather than a listener on the field wrapper) because browsers
|
|
6
|
+
// dispatch `paste` at the focused/selection node — with focus on <body> the
|
|
7
|
+
// event never bubbles through the field, which made the feature look dead
|
|
8
|
+
// unless the field was clicked first.
|
|
9
|
+
//
|
|
10
|
+
// Shared on purpose: the "single mounted pasteable field" fallback below must
|
|
11
|
+
// count every pasteable field on the page. Two private registries (one per
|
|
12
|
+
// component) would each believe they own the only field and BOTH would consume
|
|
13
|
+
// a bare Ctrl/Cmd-V.
|
|
14
|
+
const paste_targets = new Set();
|
|
15
|
+
// True only when NOTHING is focused (browsers park focus on <body>/<html>).
|
|
16
|
+
// The fallback below must never fire while the user has deliberately focused
|
|
17
|
+
// some other element — a text input obviously, but also any other widget
|
|
18
|
+
// (e.g. a different, non-pasteable field): pasting "into" the thing they
|
|
19
|
+
// focused must not teleport files to an unrelated field.
|
|
20
|
+
function is_unclaimed_focus(el) {
|
|
21
|
+
return !el || el === document.body || el === document.documentElement;
|
|
22
|
+
}
|
|
23
|
+
// Text-entry elements own their pastes even when they live INSIDE the field
|
|
24
|
+
// (consumer content via the label/description/below snippets) — an editor's
|
|
25
|
+
// image paste must insert into the editor, not upload into the field.
|
|
26
|
+
function is_text_entry(el) {
|
|
27
|
+
if (!el)
|
|
28
|
+
return false;
|
|
29
|
+
if (el.isContentEditable)
|
|
30
|
+
return true;
|
|
31
|
+
return ["INPUT", "TEXTAREA", "SELECT"].includes(el.tagName);
|
|
32
|
+
}
|
|
33
|
+
// A field hidden by CSS (kept-mounted inactive tab panel etc.) must not
|
|
34
|
+
// claim the no-focus fallback — the user would see nothing happen.
|
|
35
|
+
function is_visible(el) {
|
|
36
|
+
return el.checkVisibility?.() ?? el.offsetParent !== null;
|
|
37
|
+
}
|
|
38
|
+
// A modal/dialog (drawer, modal) takes focus on open, so `active` is the
|
|
39
|
+
// dialog panel — or a control inside it — and NEVER <body>. When the field
|
|
40
|
+
// lives in such a dialog, treat a non-text focus within that SAME dialog as
|
|
41
|
+
// unclaimed too, so a bare Ctrl/Cmd-V attaches with no prior click. Scoped to
|
|
42
|
+
// a shared [aria-modal]/[role=dialog] ancestor on purpose: it must not make
|
|
43
|
+
// the field greedy on ordinary (non-modal) pages, where a deliberately
|
|
44
|
+
// focused control elsewhere still owns its paste.
|
|
45
|
+
function shares_modal(fieldEl, active) {
|
|
46
|
+
if (!active)
|
|
47
|
+
return false;
|
|
48
|
+
const modal = fieldEl.closest?.("[aria-modal='true'],[role='dialog']");
|
|
49
|
+
return !!modal && modal.contains(active);
|
|
50
|
+
}
|
|
51
|
+
function on_document_paste(e) {
|
|
52
|
+
// someone (an editor, another paste handler) already claimed it
|
|
53
|
+
if (e.defaultPrevented)
|
|
54
|
+
return;
|
|
55
|
+
const active = document.activeElement;
|
|
56
|
+
// 1) the field holding focus always wins — unless focus sits in a
|
|
57
|
+
// text-entry element nested inside it: stand down entirely
|
|
58
|
+
for (const t of paste_targets) {
|
|
59
|
+
if (t.el.contains(active)) {
|
|
60
|
+
if (is_text_entry(active))
|
|
61
|
+
return;
|
|
62
|
+
return t.handle(e);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// 2) fall back to the SINGLE mounted + visible pasteable field for a paste
|
|
66
|
+
// NOT owned by a focused text-entry element, when focus is EITHER unclaimed
|
|
67
|
+
// (fresh page, focus on <body>) OR parked on a non-text control inside the
|
|
68
|
+
// SAME modal/dialog as the field (the drawer/modal case: the panel or the
|
|
69
|
+
// row that opened it holds focus, so <body> is never active). A bare
|
|
70
|
+
// Ctrl/Cmd-V then works with no prior click. With several fields mounted the
|
|
71
|
+
// routing would be ambiguous, so focus (a click on the field) must decide.
|
|
72
|
+
if (paste_targets.size === 1 && !is_text_entry(active)) {
|
|
73
|
+
const [t] = paste_targets;
|
|
74
|
+
if (is_visible(t.el) && (is_unclaimed_focus(active) || shares_modal(t.el, active))) {
|
|
75
|
+
t.handle(e);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Registers a pasteable field. Returns the unregister function. The document
|
|
81
|
+
* listener is installed with the first registration and removed with the last.
|
|
82
|
+
*/
|
|
83
|
+
export function registerPasteTarget(t) {
|
|
84
|
+
if (!paste_targets.size)
|
|
85
|
+
document.addEventListener("paste", on_document_paste);
|
|
86
|
+
paste_targets.add(t);
|
|
87
|
+
return () => {
|
|
88
|
+
paste_targets.delete(t);
|
|
89
|
+
if (!paste_targets.size)
|
|
90
|
+
document.removeEventListener("paste", on_document_paste);
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The file entries of a paste. Prefers `items` (lets us keep only file-kind
|
|
95
|
+
* entries, e.g. a pasted screenshot); falls back to `.files` for browsers that
|
|
96
|
+
* only populate that. Empty for a plain-text paste.
|
|
97
|
+
*/
|
|
98
|
+
export function extractClipboardFiles(e) {
|
|
99
|
+
const dt = e.clipboardData;
|
|
100
|
+
if (!dt)
|
|
101
|
+
return [];
|
|
102
|
+
const out = [];
|
|
103
|
+
for (let i = 0; i < (dt.items?.length ?? 0); i++) {
|
|
104
|
+
const it = dt.items[i];
|
|
105
|
+
if (it?.kind === "file") {
|
|
106
|
+
const f = it.getAsFile();
|
|
107
|
+
if (f)
|
|
108
|
+
out.push(f);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!out.length && dt.files?.length)
|
|
112
|
+
out.push(...dt.files);
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FieldSingleAssetMessages } from "./field-single-asset-i18n.js";
|
|
2
|
+
/**
|
|
3
|
+
* Slovak message catalog for `FieldSingleAsset`. Opt-in — English stays the built-in
|
|
4
|
+
* default, and this module is only pulled into a bundle when it is actually imported
|
|
5
|
+
* (the component itself never references it).
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```svelte
|
|
9
|
+
* <script>
|
|
10
|
+
* import {
|
|
11
|
+
* FieldSingleAsset,
|
|
12
|
+
* createFieldSingleAssetT,
|
|
13
|
+
* FIELD_SINGLE_ASSET_MESSAGES_SK,
|
|
14
|
+
* } from "@marianmeres/stuic";
|
|
15
|
+
* const t = createFieldSingleAssetT(FIELD_SINGLE_ASSET_MESSAGES_SK);
|
|
16
|
+
* </script>
|
|
17
|
+
*
|
|
18
|
+
* <FieldSingleAsset name="avatar" bind:value {processAsset} {t} />
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare const FIELD_SINGLE_ASSET_MESSAGES_SK: FieldSingleAssetMessages;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slovak message catalog for `FieldSingleAsset`. Opt-in — English stays the built-in
|
|
3
|
+
* default, and this module is only pulled into a bundle when it is actually imported
|
|
4
|
+
* (the component itself never references it).
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <script>
|
|
9
|
+
* import {
|
|
10
|
+
* FieldSingleAsset,
|
|
11
|
+
* createFieldSingleAssetT,
|
|
12
|
+
* FIELD_SINGLE_ASSET_MESSAGES_SK,
|
|
13
|
+
* } from "@marianmeres/stuic";
|
|
14
|
+
* const t = createFieldSingleAssetT(FIELD_SINGLE_ASSET_MESSAGES_SK);
|
|
15
|
+
* </script>
|
|
16
|
+
*
|
|
17
|
+
* <FieldSingleAsset name="avatar" bind:value {processAsset} {t} />
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export const FIELD_SINGLE_ASSET_MESSAGES_SK = {
|
|
21
|
+
field_req_att: "Toto pole vyžaduje pozornosť. Skontrolujte ho a skúste to znova.",
|
|
22
|
+
pick_file: "Vybrať súbor",
|
|
23
|
+
replace_file: "Nahradiť {{name}}",
|
|
24
|
+
empty_hint: "Presuňte sem súbor alebo kliknite a vyberte ho",
|
|
25
|
+
remove: "Odstrániť",
|
|
26
|
+
removed: "Súbor {{name}} bol odstránený",
|
|
27
|
+
undo: "Vrátiť späť",
|
|
28
|
+
restored: "Súbor {{name}} bol obnovený",
|
|
29
|
+
preview: "Náhľad",
|
|
30
|
+
retry: "Skúsiť nahrať znova",
|
|
31
|
+
discard: "Zahodiť",
|
|
32
|
+
cancel_upload: "Zrušiť nahrávanie",
|
|
33
|
+
uploading: "Nahráva sa {{name}}",
|
|
34
|
+
uploading_short: "Nahráva sa…",
|
|
35
|
+
uploading_progress: "Nahráva sa… {{percent}} %",
|
|
36
|
+
uploaded: "Súbor {{name}} bol nahraný",
|
|
37
|
+
upload_failed: "Nahrávanie zlyhalo",
|
|
38
|
+
upload_failed_named: "Nahrávanie súboru {{name}} zlyhalo: {{error}}",
|
|
39
|
+
invalid_type: "Tento typ súboru nie je podporovaný. Povolené: „{{accept}}“.",
|
|
40
|
+
too_large: "Súbor je príliš veľký ({{size}}). Maximum je {{max}}.",
|
|
41
|
+
single_only: "Sem je možné umiestniť iba jeden súbor. Presuňte alebo prilepte jeden súbor.",
|
|
42
|
+
// AssetsPreview
|
|
43
|
+
unable_to_preview: "Tento súbor nie je možné zobraziť",
|
|
44
|
+
download: "Stiahnuť originál",
|
|
45
|
+
close: "Zavrieť náhľad",
|
|
46
|
+
zoom_in: "Priblížiť",
|
|
47
|
+
zoom_out: "Oddialiť",
|
|
48
|
+
delete: "Odstrániť",
|
|
49
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { TranslateFn } from "../../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The built-in (English) message catalog of `FieldSingleAsset`. Also the fallback of
|
|
4
|
+
* every other bundled locale, so a locale missing a key still renders text.
|
|
5
|
+
*
|
|
6
|
+
* Placeholders are mustache-style (`{{name}}`, `{{max}}`, ...). The last six keys are
|
|
7
|
+
* the ones the embedded `AssetsPreview` looks up, so one `t` serves both.
|
|
8
|
+
*/
|
|
9
|
+
export declare const FIELD_SINGLE_ASSET_MESSAGES_EN: {
|
|
10
|
+
field_req_att: string;
|
|
11
|
+
pick_file: string;
|
|
12
|
+
replace_file: string;
|
|
13
|
+
empty_hint: string;
|
|
14
|
+
remove: string;
|
|
15
|
+
removed: string;
|
|
16
|
+
undo: string;
|
|
17
|
+
restored: string;
|
|
18
|
+
preview: string;
|
|
19
|
+
retry: string;
|
|
20
|
+
discard: string;
|
|
21
|
+
cancel_upload: string;
|
|
22
|
+
uploading: string;
|
|
23
|
+
uploading_short: string;
|
|
24
|
+
uploading_progress: string;
|
|
25
|
+
uploaded: string;
|
|
26
|
+
upload_failed: string;
|
|
27
|
+
upload_failed_named: string;
|
|
28
|
+
invalid_type: string;
|
|
29
|
+
too_large: string;
|
|
30
|
+
single_only: string;
|
|
31
|
+
unable_to_preview: string;
|
|
32
|
+
download: string;
|
|
33
|
+
close: string;
|
|
34
|
+
zoom_in: string;
|
|
35
|
+
zoom_out: string;
|
|
36
|
+
delete: string;
|
|
37
|
+
};
|
|
38
|
+
/** Every message key `FieldSingleAsset` may look up. */
|
|
39
|
+
export type FieldSingleAssetMessageKey = keyof typeof FIELD_SINGLE_ASSET_MESSAGES_EN;
|
|
40
|
+
/** A (possibly partial) catalog for one locale. */
|
|
41
|
+
export type FieldSingleAssetMessages = Record<FieldSingleAssetMessageKey, string>;
|
|
42
|
+
/**
|
|
43
|
+
* Builds the `t` prop of `FieldSingleAsset` from a message catalog. Unknown or
|
|
44
|
+
* untranslated keys fall back to `fallbackMessages` (English by default), so a catalog
|
|
45
|
+
* may safely be partial and never renders a raw key.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```svelte
|
|
49
|
+
* <script>
|
|
50
|
+
* import {
|
|
51
|
+
* FieldSingleAsset,
|
|
52
|
+
* createFieldSingleAssetT,
|
|
53
|
+
* FIELD_SINGLE_ASSET_MESSAGES_SK,
|
|
54
|
+
* } from "@marianmeres/stuic";
|
|
55
|
+
*
|
|
56
|
+
* // full locale, plus a field specific tweak
|
|
57
|
+
* const t = createFieldSingleAssetT({
|
|
58
|
+
* ...FIELD_SINGLE_ASSET_MESSAGES_SK,
|
|
59
|
+
* empty_hint: "Sem presuňte fotku",
|
|
60
|
+
* });
|
|
61
|
+
* </script>
|
|
62
|
+
*
|
|
63
|
+
* <FieldSingleAsset name="avatar" bind:value {processAsset} {t} />
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare function createFieldSingleAssetT(messages: Partial<FieldSingleAssetMessages> | Record<string, string>, fallbackMessages?: Partial<FieldSingleAssetMessages> | Record<string, string>): TranslateFn;
|
|
67
|
+
/** The component's built-in English `t`. */
|
|
68
|
+
export declare const t_default: TranslateFn;
|
|
@@ -0,0 +1,75 @@
|
|
|
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 `FieldSingleAsset`. Also the fallback of
|
|
5
|
+
* every other bundled locale, so a locale missing a key still renders text.
|
|
6
|
+
*
|
|
7
|
+
* Placeholders are mustache-style (`{{name}}`, `{{max}}`, ...). The last six keys are
|
|
8
|
+
* the ones the embedded `AssetsPreview` looks up, so one `t` serves both.
|
|
9
|
+
*/
|
|
10
|
+
export const FIELD_SINGLE_ASSET_MESSAGES_EN = {
|
|
11
|
+
field_req_att: "This field requires attention. Please review and try again.",
|
|
12
|
+
pick_file: "Choose a file",
|
|
13
|
+
replace_file: "Replace {{name}}",
|
|
14
|
+
empty_hint: "Drop a file here or click to browse",
|
|
15
|
+
remove: "Remove",
|
|
16
|
+
removed: "{{name}} removed",
|
|
17
|
+
undo: "Undo",
|
|
18
|
+
restored: "{{name}} restored",
|
|
19
|
+
preview: "Preview",
|
|
20
|
+
retry: "Retry upload",
|
|
21
|
+
discard: "Discard",
|
|
22
|
+
cancel_upload: "Cancel upload",
|
|
23
|
+
uploading: "Uploading {{name}}",
|
|
24
|
+
uploading_short: "Uploading…",
|
|
25
|
+
uploading_progress: "Uploading… {{percent}}%",
|
|
26
|
+
uploaded: "{{name}} uploaded",
|
|
27
|
+
upload_failed: "Upload failed",
|
|
28
|
+
upload_failed_named: "Upload of {{name}} failed: {{error}}",
|
|
29
|
+
invalid_type: 'This file type is not supported. Allowed: "{{accept}}".',
|
|
30
|
+
too_large: "The file is too large ({{size}}). The maximum is {{max}}.",
|
|
31
|
+
single_only: "Only one file can be placed here. Drop or paste a single file.",
|
|
32
|
+
// AssetsPreview
|
|
33
|
+
unable_to_preview: "This file cannot be previewed",
|
|
34
|
+
download: "Download original",
|
|
35
|
+
close: "Close preview window",
|
|
36
|
+
zoom_in: "Zoom in",
|
|
37
|
+
zoom_out: "Zoom out",
|
|
38
|
+
delete: "Delete",
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Builds the `t` prop of `FieldSingleAsset` from a message catalog. Unknown or
|
|
42
|
+
* untranslated keys fall back to `fallbackMessages` (English by default), so a catalog
|
|
43
|
+
* may safely be partial and never renders a raw key.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```svelte
|
|
47
|
+
* <script>
|
|
48
|
+
* import {
|
|
49
|
+
* FieldSingleAsset,
|
|
50
|
+
* createFieldSingleAssetT,
|
|
51
|
+
* FIELD_SINGLE_ASSET_MESSAGES_SK,
|
|
52
|
+
* } from "@marianmeres/stuic";
|
|
53
|
+
*
|
|
54
|
+
* // full locale, plus a field specific tweak
|
|
55
|
+
* const t = createFieldSingleAssetT({
|
|
56
|
+
* ...FIELD_SINGLE_ASSET_MESSAGES_SK,
|
|
57
|
+
* empty_hint: "Sem presuňte fotku",
|
|
58
|
+
* });
|
|
59
|
+
* </script>
|
|
60
|
+
*
|
|
61
|
+
* <FieldSingleAsset name="avatar" bind:value {processAsset} {t} />
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export function createFieldSingleAssetT(messages, fallbackMessages = FIELD_SINGLE_ASSET_MESSAGES_EN) {
|
|
65
|
+
return (k, values = null, fallback = "") => {
|
|
66
|
+
const out = messages[k] ??
|
|
67
|
+
fallbackMessages[k] ??
|
|
68
|
+
(typeof fallback === "string" ? fallback : k);
|
|
69
|
+
return isPlainObject(values)
|
|
70
|
+
? replaceMap(out, values)
|
|
71
|
+
: out;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** The component's built-in English `t`. */
|
|
75
|
+
export const t_default = createFieldSingleAssetT(FIELD_SINGLE_ASSET_MESSAGES_EN);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FieldTableMessages } from "./field-table-i18n.js";
|
|
2
|
+
/**
|
|
3
|
+
* Slovak message catalog for `FieldTable`. Opt-in — English stays the built-in
|
|
4
|
+
* default, and this module is only pulled into a bundle when it is actually imported
|
|
5
|
+
* (the component itself never references it).
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```svelte
|
|
9
|
+
* <script>
|
|
10
|
+
* import {
|
|
11
|
+
* FieldTable,
|
|
12
|
+
* createFieldTableT,
|
|
13
|
+
* FIELD_TABLE_MESSAGES_SK,
|
|
14
|
+
* } from "@marianmeres/stuic";
|
|
15
|
+
* const t = createFieldTableT(FIELD_TABLE_MESSAGES_SK);
|
|
16
|
+
* </script>
|
|
17
|
+
*
|
|
18
|
+
* <FieldTable name="items" bind:value {columns} locale="sk" {t} />
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare const FIELD_TABLE_MESSAGES_SK: FieldTableMessages;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slovak message catalog for `FieldTable`. Opt-in — English stays the built-in
|
|
3
|
+
* default, and this module is only pulled into a bundle when it is actually imported
|
|
4
|
+
* (the component itself never references it).
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <script>
|
|
9
|
+
* import {
|
|
10
|
+
* FieldTable,
|
|
11
|
+
* createFieldTableT,
|
|
12
|
+
* FIELD_TABLE_MESSAGES_SK,
|
|
13
|
+
* } from "@marianmeres/stuic";
|
|
14
|
+
* const t = createFieldTableT(FIELD_TABLE_MESSAGES_SK);
|
|
15
|
+
* </script>
|
|
16
|
+
*
|
|
17
|
+
* <FieldTable name="items" bind:value {columns} locale="sk" {t} />
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export const FIELD_TABLE_MESSAGES_SK = {
|
|
21
|
+
field_req_att: "Toto pole vyžaduje pozornosť. Skontrolujte ho a skúste to znova.",
|
|
22
|
+
add_row: "Pridať riadok",
|
|
23
|
+
empty_message: "Zatiaľ žiadne riadky",
|
|
24
|
+
row_label: "Riadok {{row}}",
|
|
25
|
+
actions_label: "Akcie",
|
|
26
|
+
move_row_up: "Posunúť riadok {{row}} vyššie",
|
|
27
|
+
move_row_down: "Posunúť riadok {{row}} nižšie",
|
|
28
|
+
remove_row: "Odstrániť riadok {{row}}",
|
|
29
|
+
added_row: "Pridaný riadok {{row}}",
|
|
30
|
+
removed_row: "Odstránený riadok {{row}}",
|
|
31
|
+
moved_row: "Riadok presunutý na pozíciu {{position}} z {{total}}",
|
|
32
|
+
unknown_row_warning: "Tento riadok nie je záznam, ktorý vie tento editor zobraziť. Zostáva nezmenený.",
|
|
33
|
+
err_rows_required: "Vyžaduje sa aspoň jeden riadok",
|
|
34
|
+
err_max_rows: "Maximálny počet riadkov je {{max}}",
|
|
35
|
+
err_cell: "Riadok {{row}}, {{column}}: {{message}}",
|
|
36
|
+
err_number: "nie je číslo",
|
|
37
|
+
err_url: "musí byť webová adresa začínajúca na http:// alebo https://",
|
|
38
|
+
err_date: "nie je platný dátum",
|
|
39
|
+
err_select_unknown: "„{{value}}“ nie je medzi možnosťami",
|
|
40
|
+
err_maxlength: "príliš dlhé (max. {{max}} znakov)",
|
|
41
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { TranslateFn } from "../../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The built-in (English) message catalog of `FieldTable`. Also the fallback of every
|
|
4
|
+
* other bundled locale, so a locale missing a key still renders text.
|
|
5
|
+
*
|
|
6
|
+
* Placeholders are mustache-style (`{{row}}`, `{{max}}`, ...).
|
|
7
|
+
*/
|
|
8
|
+
export declare const FIELD_TABLE_MESSAGES_EN: {
|
|
9
|
+
field_req_att: string;
|
|
10
|
+
add_row: string;
|
|
11
|
+
empty_message: string;
|
|
12
|
+
row_label: string;
|
|
13
|
+
actions_label: string;
|
|
14
|
+
move_row_up: string;
|
|
15
|
+
move_row_down: string;
|
|
16
|
+
remove_row: string;
|
|
17
|
+
added_row: string;
|
|
18
|
+
removed_row: string;
|
|
19
|
+
moved_row: string;
|
|
20
|
+
unknown_row_warning: string;
|
|
21
|
+
err_rows_required: string;
|
|
22
|
+
err_max_rows: string;
|
|
23
|
+
err_cell: string;
|
|
24
|
+
err_number: string;
|
|
25
|
+
err_url: string;
|
|
26
|
+
err_date: string;
|
|
27
|
+
err_select_unknown: string;
|
|
28
|
+
err_maxlength: string;
|
|
29
|
+
};
|
|
30
|
+
/** Every message key `FieldTable` may look up. */
|
|
31
|
+
export type FieldTableMessageKey = keyof typeof FIELD_TABLE_MESSAGES_EN;
|
|
32
|
+
/** A (possibly partial) catalog for one locale. */
|
|
33
|
+
export type FieldTableMessages = Record<FieldTableMessageKey, string>;
|
|
34
|
+
/**
|
|
35
|
+
* Builds the `t` prop of `FieldTable` from a message catalog. Unknown or untranslated
|
|
36
|
+
* keys fall back to `fallbackMessages` (English by default), so a catalog may safely be
|
|
37
|
+
* partial and never renders a raw key.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```svelte
|
|
41
|
+
* <script>
|
|
42
|
+
* import {
|
|
43
|
+
* FieldTable,
|
|
44
|
+
* createFieldTableT,
|
|
45
|
+
* FIELD_TABLE_MESSAGES_SK,
|
|
46
|
+
* } from "@marianmeres/stuic";
|
|
47
|
+
*
|
|
48
|
+
* // full locale, plus a field specific tweak
|
|
49
|
+
* const t = createFieldTableT({
|
|
50
|
+
* ...FIELD_TABLE_MESSAGES_SK,
|
|
51
|
+
* add_row: "Pridať položku",
|
|
52
|
+
* });
|
|
53
|
+
* </script>
|
|
54
|
+
*
|
|
55
|
+
* <FieldTable name="items" bind:value {columns} {t} />
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function createFieldTableT(messages: Partial<FieldTableMessages> | Record<string, string>, fallbackMessages?: Partial<FieldTableMessages> | Record<string, string>): TranslateFn;
|
|
59
|
+
/** The component's built-in English `t`. */
|
|
60
|
+
export declare const t_default: TranslateFn;
|
|
@@ -0,0 +1,66 @@
|
|
|
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 `FieldTable`. Also the fallback of every
|
|
5
|
+
* other bundled locale, so a locale missing a key still renders text.
|
|
6
|
+
*
|
|
7
|
+
* Placeholders are mustache-style (`{{row}}`, `{{max}}`, ...).
|
|
8
|
+
*/
|
|
9
|
+
export const FIELD_TABLE_MESSAGES_EN = {
|
|
10
|
+
field_req_att: "This field requires attention. Please review and try again.",
|
|
11
|
+
add_row: "Add row",
|
|
12
|
+
empty_message: "No rows yet",
|
|
13
|
+
row_label: "Row {{row}}",
|
|
14
|
+
actions_label: "Actions",
|
|
15
|
+
move_row_up: "Move row {{row}} up",
|
|
16
|
+
move_row_down: "Move row {{row}} down",
|
|
17
|
+
remove_row: "Remove row {{row}}",
|
|
18
|
+
added_row: "Row {{row}} added",
|
|
19
|
+
removed_row: "Row {{row}} removed",
|
|
20
|
+
moved_row: "Row moved to position {{position}} of {{total}}",
|
|
21
|
+
unknown_row_warning: "This row is not a record this editor can show. It is kept unchanged.",
|
|
22
|
+
err_rows_required: "At least one row is required",
|
|
23
|
+
err_max_rows: "Maximum number of rows is {{max}}",
|
|
24
|
+
err_cell: "Row {{row}}, {{column}}: {{message}}",
|
|
25
|
+
err_number: "not a number",
|
|
26
|
+
err_url: "must be a web address starting with http:// or https://",
|
|
27
|
+
err_date: "not a valid date",
|
|
28
|
+
err_select_unknown: "“{{value}}” is not one of the choices",
|
|
29
|
+
err_maxlength: "too long (max {{max}} characters)",
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Builds the `t` prop of `FieldTable` from a message catalog. Unknown or untranslated
|
|
33
|
+
* keys fall back to `fallbackMessages` (English by default), so a catalog may safely be
|
|
34
|
+
* partial and never renders a raw key.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```svelte
|
|
38
|
+
* <script>
|
|
39
|
+
* import {
|
|
40
|
+
* FieldTable,
|
|
41
|
+
* createFieldTableT,
|
|
42
|
+
* FIELD_TABLE_MESSAGES_SK,
|
|
43
|
+
* } from "@marianmeres/stuic";
|
|
44
|
+
*
|
|
45
|
+
* // full locale, plus a field specific tweak
|
|
46
|
+
* const t = createFieldTableT({
|
|
47
|
+
* ...FIELD_TABLE_MESSAGES_SK,
|
|
48
|
+
* add_row: "Pridať položku",
|
|
49
|
+
* });
|
|
50
|
+
* </script>
|
|
51
|
+
*
|
|
52
|
+
* <FieldTable name="items" bind:value {columns} {t} />
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function createFieldTableT(messages, fallbackMessages = FIELD_TABLE_MESSAGES_EN) {
|
|
56
|
+
return (k, values = null, fallback = "") => {
|
|
57
|
+
const out = messages[k] ??
|
|
58
|
+
fallbackMessages[k] ??
|
|
59
|
+
(typeof fallback === "string" ? fallback : k);
|
|
60
|
+
return isPlainObject(values)
|
|
61
|
+
? replaceMap(out, values)
|
|
62
|
+
: out;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** The component's built-in English `t`. */
|
|
66
|
+
export const t_default = createFieldTableT(FIELD_TABLE_MESSAGES_EN);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locale-aware number parsing and formatting for `FieldTable`'s `number` cells.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions (no DOM), so they can be unit-tested in the fast node project.
|
|
5
|
+
* The rule they implement is "never guess": a typed value is either read as one
|
|
6
|
+
* unambiguous number, or rejected.
|
|
7
|
+
*/
|
|
8
|
+
/** A parse outcome — `null` is "blank" (an empty cell), never an error. */
|
|
9
|
+
export type ParsedCellNumber = {
|
|
10
|
+
ok: true;
|
|
11
|
+
value: number | null;
|
|
12
|
+
} | {
|
|
13
|
+
ok: false;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The decimal separator `Intl` uses for `locale` (`"."` for `en`, `","` for `sk`).
|
|
17
|
+
* `undefined` means the runtime's default locale. An unknown locale falls back to `"."`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function decimalSeparator(locale?: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Parses what a user typed into a number cell.
|
|
22
|
+
*
|
|
23
|
+
* - Whitespace anywhere (including NBSP / narrow NBSP) is stripped: `"1 000"` → `1000`.
|
|
24
|
+
* - `"."` is always accepted as the decimal separator.
|
|
25
|
+
* - `","` is accepted as the decimal separator **only** when `locale` uses it. Under an
|
|
26
|
+
* English locale `"1,000"` is rejected — it is never silently read as a thousands group.
|
|
27
|
+
* - Two separators (`"1.2.3"`, `"1,2,3"`, `"1,000.5"`) are rejected.
|
|
28
|
+
* - A Unicode minus (`"−5"`, what `Intl` formats in some locales) reads as a minus sign.
|
|
29
|
+
* - Blank is `{ ok: true, value: null }`.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* parseCellNumber("4.2", "sk"); // { ok: true, value: 4.2 }
|
|
34
|
+
* parseCellNumber("4,2", "sk"); // { ok: true, value: 4.2 }
|
|
35
|
+
* parseCellNumber("4,2", "en"); // { ok: false }
|
|
36
|
+
* parseCellNumber("1 000", "en"); // { ok: true, value: 1000 }
|
|
37
|
+
* parseCellNumber("", "en"); // { ok: true, value: null }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseCellNumber(raw: string, locale?: string): ParsedCellNumber;
|
|
41
|
+
/**
|
|
42
|
+
* Formats a number for display in a number cell: no grouping, up to 20 fraction
|
|
43
|
+
* digits (so `4.2` is never rounded away), Latin digits, the locale's decimal
|
|
44
|
+
* separator. A Slovak user sees `4,2` for `4.2`; an English one `4.2`.
|
|
45
|
+
*
|
|
46
|
+
* A non-finite input yields `""`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function formatCellNumber(n: number, locale?: string): string;
|