@cahyo-dimas/freeday 1.38.0 → 1.40.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/CHANGELOG.md +59 -0
- package/COMPONENTS.md +22 -7
- package/adapters/blazor/FdyCascade.razor +3 -1
- package/adapters/blazor/FdyCascade.razor.cs +10 -2
- package/adapters/blazor/freeday-blazor.js +5 -0
- package/dist/freeday-carousel.js +18 -2
- package/dist/freeday-cascade.js +18 -2
- package/dist/freeday-cfl.js +16 -1
- package/dist/freeday-form.js +15 -4
- package/dist/freeday-mask.js +18 -2
- package/dist/freeday-stepper.js +17 -1
- package/dist/freeday-table.js +39 -13
- package/dist/freeday-toast.js +16 -1
- package/dist/freeday-upload.js +28 -7
- package/dist/freeday.bundle.css +6 -0
- package/dist/freeday.css +6 -0
- package/dist/freeday.js +185 -33
- package/package.json +2 -2
- package/src/components/datepicker.css +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,65 @@
|
|
|
3
3
|
Semua perubahan penting dicatat di sini. Format longgar mengikuti
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/); tiap versi = git tag.
|
|
5
5
|
|
|
6
|
+
## [1.40.0] — 2026-08-19
|
|
7
|
+
One note from the back-office app (IDU_EMATE_APPL_WEB, #017).
|
|
8
|
+
### Fixed
|
|
9
|
+
- **A picker inside a `.fdy-field` now fills it.** `.fdy-field`, `.fdy-combo` and
|
|
10
|
+
`.fdy-input-group` all cap at 22rem; `.fdy-datepicker` capped at 14rem and `.fdy-timepicker`
|
|
11
|
+
at 11rem. In a two-column form grid that left a **224px** date box beside a **319px** combo —
|
|
12
|
+
measured, not eyeballed — and the row read as ragged with no fix available to the consuming
|
|
13
|
+
app, because the cap sits on the component. `.fdy-field > .fdy-datepicker` / `> .fdy-timepicker`
|
|
14
|
+
/ `> .fdy-daterange` drop the cap; a picker standing on its own keeps its narrower width, which
|
|
15
|
+
is right for a short value.
|
|
16
|
+
|
|
17
|
+
### Added — guards
|
|
18
|
+
- **The enhancer string contract is asserted in a real browser** (#016). 1.39.0 moved every
|
|
19
|
+
user-facing string in **nine** enhancers into a `TEXT` table read through `textOf()`, and only
|
|
20
|
+
**one of the nine** — upload — had any browser coverage at all. The node guard proves the
|
|
21
|
+
literals *sit* in the table; it reads the source as text and cannot prove the wiring *runs*, so a
|
|
22
|
+
`zone` out of scope or a `getAttribute` on a non-element would have thrown at init with all 51
|
|
23
|
+
node tests still green. `browser/text-override.mjs` initialises table, stepper, mask, carousel,
|
|
24
|
+
cascade, form and toast on real markup and asserts **both halves**: the documented Indonesian
|
|
25
|
+
default still renders, and `data-fdy-text-*` wins. The default is a contract too — an enhancer
|
|
26
|
+
that quietly turned English would be a breaking change wearing a bugfix's clothes.
|
|
27
|
+
Mutation-tested: making `textOf()` ignore the attribute fails it by name and value.
|
|
28
|
+
|
|
29
|
+
## [1.39.0] — 2026-08-19
|
|
30
|
+
The sweep #013 §2 filed and #015 restated: the vanilla enhancers' strings are reachable now.
|
|
31
|
+
### Added
|
|
32
|
+
- **Every enhancer string is overridable with `data-fdy-text-<key>`** (#016). The defaults stay
|
|
33
|
+
Indonesian — that is documented and deliberate for the raw path, and changing it would break
|
|
34
|
+
every app that adopted it — but they were also *unreachable*, and that is the half that was
|
|
35
|
+
wrong. A Blazor app meets these enhancers through `FreedayBlazor.initAll`, which runs every one
|
|
36
|
+
of them, while COMPONENTS.md promises Blazor is English throughout; both promises can only hold
|
|
37
|
+
if the default stays and the string can be changed. Each enhancer now keeps its strings in one
|
|
38
|
+
`TEXT` table read through `textOf()`. `{n}`, `{from}`, `{to}`, `{total}`, `{name}`, `{max}` and
|
|
39
|
+
`{label}` are substituted where the string supports them. Validation messages take the same shape
|
|
40
|
+
on the `<form>`, narrower than the per-field `data-fdy-msg-<alias>` that still wins;
|
|
41
|
+
`Freeday.toast()` takes `closeLabel`.
|
|
42
|
+
- **34 strings across 9 files**, not the 19 across 7 a word-list scan had counted — `carousel`
|
|
43
|
+
(`Slide {n}`), `cascade` (`Kembali satu tingkat`), `mask` (`Tampilkan kata sandi`), `stepper`
|
|
44
|
+
(`Selesai` / `Lanjut`) and four upload status strings were all invisible to it. #015 had just
|
|
45
|
+
finished making this point about `\bcari\b` and `pencarian`; the recount was found by scanning
|
|
46
|
+
by SINK — what lands in a `textContent`, an `aria-label`, a `title` or a `placeholder` — which
|
|
47
|
+
cannot be fooled by an affix.
|
|
48
|
+
### Fixed
|
|
49
|
+
- **Blazor's `FdyCascade` rendered Indonesian** (#016). `Label` and `Placeholder` were `string?`
|
|
50
|
+
with no default, so an app that did not set them emitted `data-label=""` and the enhancer fell
|
|
51
|
+
back to `Pilih` / `Pilih…`. Vue and React have defaulted to `Select` / `Select…` since #015 —
|
|
52
|
+
same drift as #009 and #015, third time, and the parameter existing is what made it invisible.
|
|
53
|
+
They are non-nullable with English defaults now, and `BackLabel` / `SubmenuLabel` reach the two
|
|
54
|
+
strings the enhancer builds itself, which had no route from .NET at all.
|
|
55
|
+
- **`Freeday.toast()` from Blazor labelled its close button `Tutup`.** `FreedayBlazor.toast` now
|
|
56
|
+
defaults `closeLabel` to `Close` — in the bridge, not the enhancer, so a hand-written page still
|
|
57
|
+
gets the documented Indonesian.
|
|
58
|
+
### Added — guards
|
|
59
|
+
- **An enhancer string is overridable, not hard-coded** (#016). `npm test` asserts that a literal
|
|
60
|
+
reaching a `textContent`, `aria-label`, `title` or `placeholder` lives in that file's `TEXT`
|
|
61
|
+
table. Scoped by SINK rather than by a word list, deliberately: a second word list would have the
|
|
62
|
+
same hole one affix over, and this one is also blind to no language. Mutation-tested — putting
|
|
63
|
+
`'Tutup'` back into the toast fails it by file and line.
|
|
64
|
+
|
|
6
65
|
## [1.38.0] — 2026-08-19
|
|
7
66
|
One note from the back-office app (IDU_EMATE_APPL_WEB, #015).
|
|
8
67
|
### Fixed
|
package/COMPONENTS.md
CHANGED
|
@@ -759,14 +759,29 @@ pagination. Needs `freeday-table.js`. Wrap the whole thing in `.fdy-datatable` +
|
|
|
759
759
|
offer) carrying `data-fdy-table-page-size`; the enhancer only wires it, and listens for both
|
|
760
760
|
`change` and the `fdy-change` a `.fdy-combo` emits, so either kind works.
|
|
761
761
|
- Sort values: put the raw value in `data-sort-value` when the cell text is formatted.
|
|
762
|
-
- **Language caveat:** every user-visible string the **vanilla enhancers** write is Indonesian
|
|
763
|
-
the table's footer and bulk count (`Menampilkan 1–5 dari 7`, `N dipilih`), its pager and
|
|
764
|
-
UI (`Sebelumnya`, `Berikutnya`, `Filter kolom`, `Berisi teks`, `Reset`, `Tutup`), and the
|
|
765
|
-
|
|
762
|
+
- **Language caveat:** every user-visible string the **vanilla enhancers** write is Indonesian by
|
|
763
|
+
default — the table's footer and bulk count (`Menampilkan 1–5 dari 7`, `N dipilih`), its pager and
|
|
764
|
+
filter UI (`Sebelumnya`, `Berikutnya`, `Filter kolom`, `Berisi teks`, `Reset`, `Tutup`), and the
|
|
765
|
+
same elsewhere (`Format tidak valid.`, `Tampilkan kata sandi`, `Menunggu server…`). The
|
|
766
766
|
Vue/React/Blazor components are English throughout, so **an English app should use the typed
|
|
767
|
-
wrapper
|
|
768
|
-
|
|
769
|
-
`
|
|
767
|
+
wrapper**.
|
|
768
|
+
- **Overriding an enhancer's strings.** Each enhancer keeps its strings in one `TEXT` table and
|
|
769
|
+
reads them through `textOf()`, so any of them can be replaced per element with
|
|
770
|
+
**`data-fdy-text-<key>`** — no forking, and no rendering the nodes yourself:
|
|
771
|
+
|
|
772
|
+
```html
|
|
773
|
+
<div data-fdy-table
|
|
774
|
+
data-fdy-text-info="Showing {from}–{to} of {total}"
|
|
775
|
+
data-fdy-text-selected="{n} selected"
|
|
776
|
+
data-fdy-text-filter="Column filter">…</div>
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
`{n}`, `{from}`, `{to}`, `{total}`, `{name}`, `{max}` and `{label}` are substituted where the
|
|
780
|
+
string supports them. Validation messages take the same shape on the `<form>`
|
|
781
|
+
(`data-fdy-text-required`, `data-fdy-text-type`, …), narrower than the per-field
|
|
782
|
+
`data-fdy-msg-<alias>` that still wins. `Freeday.toast()` takes `closeLabel` in its options
|
|
783
|
+
object. `npm test` asserts no enhancer string is written outside its `TEXT` table, so a new one
|
|
784
|
+
arrives overridable or not at all.
|
|
770
785
|
|
|
771
786
|
**Testing note:** a column's filter button and the dialog it opens deliberately share one
|
|
772
787
|
accessible name (`Filter <column>`) — a dialog named after its trigger is the normal pattern. In a
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
data-label="@Label"
|
|
10
10
|
data-value="@Value"
|
|
11
11
|
data-placeholder="@Placeholder"
|
|
12
|
-
data-separator="@Separator"
|
|
12
|
+
data-separator="@Separator"
|
|
13
|
+
data-fdy-text-back="@BackLabel"
|
|
14
|
+
data-fdy-text-submenu="@SubmenuLabel">
|
|
13
15
|
<ul>
|
|
14
16
|
@foreach (FdyCascadeNode node in Nodes)
|
|
15
17
|
{
|
|
@@ -12,12 +12,20 @@ public partial class FdyCascade
|
|
|
12
12
|
[Parameter] public string? Value { get; set; }
|
|
13
13
|
[Parameter] public EventCallback<string?> ValueChanged { get; set; }
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
[Parameter] public string
|
|
15
|
+
/// <summary>Trigger label. English by default, matching Vue and React.</summary>
|
|
16
|
+
[Parameter] public string Label { get; set; } = "Select";
|
|
17
|
+
/// <summary>Empty-value placeholder. English by default, matching Vue and React.</summary>
|
|
18
|
+
[Parameter] public string Placeholder { get; set; } = "Select…";
|
|
17
19
|
|
|
18
20
|
/// <summary>Separator between labels in the displayed path (default " / ").</summary>
|
|
19
21
|
[Parameter] public string Separator { get; set; } = " / ";
|
|
20
22
|
|
|
23
|
+
/// <summary>aria-label for the button that goes up one level. Matches Vue and React.</summary>
|
|
24
|
+
[Parameter] public string BackLabel { get; set; } = "Back one level";
|
|
25
|
+
|
|
26
|
+
/// <summary>aria-label for a branch row. <c>{label}</c> is replaced with the node label.</summary>
|
|
27
|
+
[Parameter] public string SubmenuLabel { get; set; } = "{label}, submenu";
|
|
28
|
+
|
|
21
29
|
protected override ValueTask HydrateAsync() => JS.InvokeVoidAsync("FreedayCascade.init", Root);
|
|
22
30
|
|
|
23
31
|
protected override async ValueTask OnHydratedAsync()
|
|
@@ -99,6 +99,11 @@
|
|
|
99
99
|
|
|
100
100
|
// Passthrough to the Freeday toast API.
|
|
101
101
|
function toast(options) {
|
|
102
|
+
/* The enhancer's own default is Indonesian — deliberate for the raw path, wrong for a Blazor
|
|
103
|
+
* app, which COMPONENTS.md promises is English throughout. Defaulted here so the promise is
|
|
104
|
+
* kept without changing what a hand-written page gets. */
|
|
105
|
+
options = options || {};
|
|
106
|
+
if (options.closeLabel == null || options.closeLabel === '') options.closeLabel = 'Close';
|
|
102
107
|
if (window.Freeday && typeof window.Freeday.toast === 'function') window.Freeday.toast(options);
|
|
103
108
|
}
|
|
104
109
|
|
package/dist/freeday-carousel.js
CHANGED
|
@@ -17,6 +17,22 @@
|
|
|
17
17
|
(function () {
|
|
18
18
|
'use strict';
|
|
19
19
|
|
|
20
|
+
|
|
21
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
22
|
+
* path — and every one overridable per element, so a host that speaks another language (the
|
|
23
|
+
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
24
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
25
|
+
var TEXT = {
|
|
26
|
+
position: '{n} dari {total}',
|
|
27
|
+
slide: 'Slide {n}'
|
|
28
|
+
};
|
|
29
|
+
function textOf(root, key, vars) {
|
|
30
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
31
|
+
var s = custom != null && custom !== '' ? custom : TEXT[key];
|
|
32
|
+
if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
|
|
33
|
+
return s;
|
|
34
|
+
}
|
|
35
|
+
|
|
20
36
|
function initCarousel(root) {
|
|
21
37
|
if (root.dataset.fdyCarouselReady === '1') return;
|
|
22
38
|
root.dataset.fdyCarouselReady = '1';
|
|
@@ -30,7 +46,7 @@
|
|
|
30
46
|
var index = 0;
|
|
31
47
|
|
|
32
48
|
slides.forEach(function (s, i) {
|
|
33
|
-
if (!s.hasAttribute('aria-label')) s.setAttribute('aria-label', (
|
|
49
|
+
if (!s.hasAttribute('aria-label')) s.setAttribute('aria-label', textOf(root, 'position', { n: i + 1, total: slides.length }));
|
|
34
50
|
});
|
|
35
51
|
|
|
36
52
|
var dots = [];
|
|
@@ -39,7 +55,7 @@
|
|
|
39
55
|
var b = document.createElement('button');
|
|
40
56
|
b.type = 'button';
|
|
41
57
|
b.className = 'fdy-carousel__dot';
|
|
42
|
-
b.setAttribute('aria-label',
|
|
58
|
+
b.setAttribute('aria-label', textOf(root, 'slide', { n: i + 1 }));
|
|
43
59
|
b.addEventListener('click', function () { goTo(i); });
|
|
44
60
|
dotsWrap.appendChild(b);
|
|
45
61
|
dots.push(b);
|
package/dist/freeday-cascade.js
CHANGED
|
@@ -53,6 +53,22 @@
|
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
|
|
57
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
58
|
+
* path — and every one overridable per element, so a host that speaks another language (the
|
|
59
|
+
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
60
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
61
|
+
var TEXT = {
|
|
62
|
+
back: 'Kembali satu tingkat',
|
|
63
|
+
submenu: '{label}, submenu'
|
|
64
|
+
};
|
|
65
|
+
function textOf(root, key, vars) {
|
|
66
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
67
|
+
var s = custom != null && custom !== '' ? custom : TEXT[key];
|
|
68
|
+
if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
|
|
69
|
+
return s;
|
|
70
|
+
}
|
|
71
|
+
|
|
56
72
|
function initCascade(wrap) {
|
|
57
73
|
if (wrap.dataset.fdyCascadeReady === '1') return;
|
|
58
74
|
wrap.dataset.fdyCascadeReady = '1';
|
|
@@ -84,7 +100,7 @@
|
|
|
84
100
|
var back = document.createElement('button');
|
|
85
101
|
back.type = 'button';
|
|
86
102
|
back.className = 'fdy-cascade__back';
|
|
87
|
-
back.setAttribute('aria-label',
|
|
103
|
+
back.setAttribute('aria-label', textOf(wrap, 'back'));
|
|
88
104
|
back.innerHTML = BACK;
|
|
89
105
|
back.hidden = true;
|
|
90
106
|
var crumb = document.createElement('span');
|
|
@@ -124,7 +140,7 @@
|
|
|
124
140
|
li.setAttribute('data-index', String(i));
|
|
125
141
|
var isBranch = !!node.children;
|
|
126
142
|
li.setAttribute('aria-selected', (!isBranch && node.value === selectedValue) ? 'true' : 'false');
|
|
127
|
-
if (isBranch) li.setAttribute('aria-label', node.label
|
|
143
|
+
if (isBranch) li.setAttribute('aria-label', textOf(wrap, 'submenu', { label: node.label }));
|
|
128
144
|
var lbl = document.createElement('span');
|
|
129
145
|
lbl.className = 'fdy-cascade__opt-label';
|
|
130
146
|
lbl.textContent = node.label;
|
package/dist/freeday-cfl.js
CHANGED
|
@@ -33,6 +33,21 @@
|
|
|
33
33
|
.toLowerCase();
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
|
|
37
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
38
|
+
* path — and every one overridable per element, so a host that speaks another language (the
|
|
39
|
+
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
40
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
41
|
+
var TEXT = {
|
|
42
|
+
selected: '{n} dipilih'
|
|
43
|
+
};
|
|
44
|
+
function textOf(root, key, vars) {
|
|
45
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
46
|
+
var s = custom != null && custom !== '' ? custom : TEXT[key];
|
|
47
|
+
if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
|
|
48
|
+
return s;
|
|
49
|
+
}
|
|
50
|
+
|
|
36
51
|
function initDialog(dialog) {
|
|
37
52
|
if (dialog.dataset.fdyCflReady === '1') return;
|
|
38
53
|
dialog.dataset.fdyCflReady = '1';
|
|
@@ -70,7 +85,7 @@
|
|
|
70
85
|
function updateCount() {
|
|
71
86
|
if (!countEl) return;
|
|
72
87
|
var n = rows.filter(function (r) { return r.getAttribute('aria-selected') === 'true'; }).length;
|
|
73
|
-
countEl.textContent = n
|
|
88
|
+
countEl.textContent = textOf(dialog, 'selected', { n: n });
|
|
74
89
|
}
|
|
75
90
|
|
|
76
91
|
function fillGroup(detail) {
|
package/dist/freeday-form.js
CHANGED
|
@@ -38,7 +38,12 @@
|
|
|
38
38
|
badInput: 'type',
|
|
39
39
|
customError: 'mismatch'
|
|
40
40
|
};
|
|
41
|
-
|
|
41
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
42
|
+
* path — and overridable at three levels, narrowest first: `data-fdy-msg-<alias>` on the field,
|
|
43
|
+
* `data-fdy-msg` on the field, then `data-fdy-text-<alias>` on the FORM. The form level is what
|
|
44
|
+
* a host in another language needs: it sets nine messages once instead of on every input.
|
|
45
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
46
|
+
var TEXT = {
|
|
42
47
|
required: 'Wajib diisi.',
|
|
43
48
|
type: 'Format tidak valid.',
|
|
44
49
|
pattern: 'Format tidak sesuai.',
|
|
@@ -47,8 +52,13 @@
|
|
|
47
52
|
min: 'Nilai terlalu kecil.',
|
|
48
53
|
max: 'Nilai terlalu besar.',
|
|
49
54
|
step: 'Nilai tidak sesuai kelipatan.',
|
|
50
|
-
mismatch: 'Nilai tidak cocok.'
|
|
55
|
+
mismatch: 'Nilai tidak cocok.',
|
|
56
|
+
invalid: 'Tidak valid.'
|
|
51
57
|
};
|
|
58
|
+
function textOf(root, key) {
|
|
59
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
60
|
+
return custom != null && custom !== '' ? custom : TEXT[key];
|
|
61
|
+
}
|
|
52
62
|
var VALIDITY_KEYS = ['valueMissing', 'typeMismatch', 'patternMismatch', 'tooShort', 'tooLong', 'rangeUnderflow', 'rangeOverflow', 'stepMismatch', 'badInput'];
|
|
53
63
|
|
|
54
64
|
function isCandidate(el) {
|
|
@@ -82,7 +92,7 @@
|
|
|
82
92
|
if (matchSel) {
|
|
83
93
|
var other = document.querySelector(matchSel);
|
|
84
94
|
if (other && el.value !== other.value) {
|
|
85
|
-
el.setCustomValidity(el.getAttribute('data-fdy-msg-mismatch') || el.getAttribute('data-fdy-msg') ||
|
|
95
|
+
el.setCustomValidity(el.getAttribute('data-fdy-msg-mismatch') || el.getAttribute('data-fdy-msg') || textOf(el.form || el.closest('form'), 'mismatch'));
|
|
86
96
|
}
|
|
87
97
|
}
|
|
88
98
|
if (el.validity.valid) return '';
|
|
@@ -93,7 +103,8 @@
|
|
|
93
103
|
}
|
|
94
104
|
}
|
|
95
105
|
var custom = alias ? el.getAttribute('data-fdy-msg-' + alias) : null;
|
|
96
|
-
|
|
106
|
+
var form = el.form || el.closest('form');
|
|
107
|
+
return custom || el.getAttribute('data-fdy-msg') || (alias && textOf(form, alias)) || el.validationMessage || textOf(form, 'invalid');
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
function paint(el, message) {
|
package/dist/freeday-mask.js
CHANGED
|
@@ -50,6 +50,22 @@
|
|
|
50
50
|
return out;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
|
|
54
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
55
|
+
* path — and every one overridable per element, so a host that speaks another language (the
|
|
56
|
+
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
57
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
58
|
+
var TEXT = {
|
|
59
|
+
show: 'Tampilkan kata sandi',
|
|
60
|
+
hide: 'Sembunyikan kata sandi'
|
|
61
|
+
};
|
|
62
|
+
function textOf(root, key, vars) {
|
|
63
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
64
|
+
var s = custom != null && custom !== '' ? custom : TEXT[key];
|
|
65
|
+
if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
|
|
66
|
+
return s;
|
|
67
|
+
}
|
|
68
|
+
|
|
53
69
|
function initMask(el) {
|
|
54
70
|
if (el.dataset.fdyMaskReady === '1') return;
|
|
55
71
|
el.dataset.fdyMaskReady = '1';
|
|
@@ -85,13 +101,13 @@
|
|
|
85
101
|
btn.type = 'button';
|
|
86
102
|
btn.className = 'fdy-input-group__btn';
|
|
87
103
|
btn.setAttribute('aria-pressed', 'false');
|
|
88
|
-
btn.setAttribute('aria-label',
|
|
104
|
+
btn.setAttribute('aria-label', textOf(input, 'show'));
|
|
89
105
|
btn.innerHTML = EYE;
|
|
90
106
|
btn.addEventListener('click', function () {
|
|
91
107
|
var reveal = input.type === 'password';
|
|
92
108
|
input.type = reveal ? 'text' : 'password';
|
|
93
109
|
btn.setAttribute('aria-pressed', reveal ? 'true' : 'false');
|
|
94
|
-
btn.setAttribute('aria-label', reveal ?
|
|
110
|
+
btn.setAttribute('aria-label', reveal ? textOf(input, 'hide') : textOf(input, 'show'));
|
|
95
111
|
btn.innerHTML = reveal ? EYE_OFF : EYE;
|
|
96
112
|
input.focus();
|
|
97
113
|
});
|
package/dist/freeday-stepper.js
CHANGED
|
@@ -18,6 +18,22 @@
|
|
|
18
18
|
|
|
19
19
|
var CHECK = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"></path></svg>';
|
|
20
20
|
|
|
21
|
+
|
|
22
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
23
|
+
* path — and every one overridable per element, so a host that speaks another language (the
|
|
24
|
+
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
25
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
26
|
+
var TEXT = {
|
|
27
|
+
done: 'Selesai',
|
|
28
|
+
next: 'Lanjut'
|
|
29
|
+
};
|
|
30
|
+
function textOf(root, key, vars) {
|
|
31
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
32
|
+
var s = custom != null && custom !== '' ? custom : TEXT[key];
|
|
33
|
+
if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
|
|
34
|
+
return s;
|
|
35
|
+
}
|
|
36
|
+
|
|
21
37
|
function initStepper(root) {
|
|
22
38
|
if (root.dataset.fdyStepperReady === '1') return;
|
|
23
39
|
root.dataset.fdyStepperReady = '1';
|
|
@@ -52,7 +68,7 @@
|
|
|
52
68
|
});
|
|
53
69
|
panels.forEach(function (p, i) { p.hidden = i !== active; });
|
|
54
70
|
if (prevBtn) prevBtn.disabled = active === 0;
|
|
55
|
-
if (nextBtn) nextBtn.textContent = active === steps.length - 1 ? '
|
|
71
|
+
if (nextBtn) nextBtn.textContent = active === steps.length - 1 ? textOf(root, 'done') : (nextLabel || textOf(root, 'next'));
|
|
56
72
|
root.dispatchEvent(new CustomEvent('fdy-step-change', { bubbles: true, detail: { index: active } }));
|
|
57
73
|
}
|
|
58
74
|
|
package/dist/freeday-table.js
CHANGED
|
@@ -33,6 +33,32 @@
|
|
|
33
33
|
: cell.textContent).trim();
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
|
|
37
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
38
|
+
* path — and every one overridable per element, so a host that speaks another language (the
|
|
39
|
+
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
40
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
41
|
+
var TEXT = {
|
|
42
|
+
prev: 'Sebelumnya',
|
|
43
|
+
next: 'Berikutnya',
|
|
44
|
+
selected: '{n} dipilih',
|
|
45
|
+
filter: 'Filter kolom',
|
|
46
|
+
filterText: 'Berisi teks',
|
|
47
|
+
filterTextPlaceholder: 'Berisi…',
|
|
48
|
+
filterEnum: 'Tampilkan nilai',
|
|
49
|
+
filterRange: 'Rentang nilai',
|
|
50
|
+
reset: 'Reset',
|
|
51
|
+
close: 'Tutup',
|
|
52
|
+
rows: '{n} baris',
|
|
53
|
+
info: 'Menampilkan {from}–{to} dari {total}'
|
|
54
|
+
};
|
|
55
|
+
function textOf(root, key, vars) {
|
|
56
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
57
|
+
var s = custom != null && custom !== '' ? custom : TEXT[key];
|
|
58
|
+
if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
|
|
59
|
+
return s;
|
|
60
|
+
}
|
|
61
|
+
|
|
36
62
|
function initTable(root) {
|
|
37
63
|
if (root.dataset.fdyTableReady === '1') return;
|
|
38
64
|
var table = root.querySelector('table.fdy-table');
|
|
@@ -157,7 +183,7 @@
|
|
|
157
183
|
ul.appendChild(li);
|
|
158
184
|
}
|
|
159
185
|
|
|
160
|
-
addBtn('‹', page - 1, { disabled: page === 1, label: '
|
|
186
|
+
addBtn('‹', page - 1, { disabled: page === 1, label: textOf(root, 'prev') });
|
|
161
187
|
var pages = [];
|
|
162
188
|
for (var p = 1; p <= totalPages; p++) {
|
|
163
189
|
if (p === 1 || p === totalPages || (p >= page - 1 && p <= page + 1)) pages.push(p);
|
|
@@ -167,7 +193,7 @@
|
|
|
167
193
|
if (p === '…') addEllipsis();
|
|
168
194
|
else addBtn(String(p), p, { current: p === page });
|
|
169
195
|
});
|
|
170
|
-
addBtn('›', page + 1, { disabled: page === totalPages, label: '
|
|
196
|
+
addBtn('›', page + 1, { disabled: page === totalPages, label: textOf(root, 'next') });
|
|
171
197
|
pagerEl.appendChild(ul);
|
|
172
198
|
}
|
|
173
199
|
|
|
@@ -182,7 +208,7 @@
|
|
|
182
208
|
if (!bulkBar) return;
|
|
183
209
|
var n = selectedRows().length;
|
|
184
210
|
bulkBar.hidden = n === 0;
|
|
185
|
-
if (bulkCount) bulkCount.textContent = n
|
|
211
|
+
if (bulkCount) bulkCount.textContent = textOf(root, 'selected', { n: n });
|
|
186
212
|
}
|
|
187
213
|
function clearSelection() {
|
|
188
214
|
allRows.forEach(function (r) {
|
|
@@ -238,15 +264,15 @@
|
|
|
238
264
|
var pop = document.createElement('div');
|
|
239
265
|
pop.className = 'fdy-filter';
|
|
240
266
|
pop.setAttribute('role', 'dialog');
|
|
241
|
-
pop.setAttribute('aria-label',
|
|
267
|
+
pop.setAttribute('aria-label', textOf(root, 'filter'));
|
|
242
268
|
var f = colFilters[idx] || { type: type };
|
|
243
269
|
|
|
244
270
|
if (type === 'text') {
|
|
245
|
-
pop.appendChild(filterTitle(
|
|
271
|
+
pop.appendChild(filterTitle(textOf(root, 'filterText')));
|
|
246
272
|
var inp = document.createElement('input');
|
|
247
273
|
inp.className = 'fdy-input';
|
|
248
274
|
inp.type = 'search';
|
|
249
|
-
inp.placeholder = '
|
|
275
|
+
inp.placeholder = textOf(root, 'filterTextPlaceholder');
|
|
250
276
|
inp.value = f.text || '';
|
|
251
277
|
inp.addEventListener('input', function () {
|
|
252
278
|
f.text = inp.value.trim();
|
|
@@ -256,7 +282,7 @@
|
|
|
256
282
|
});
|
|
257
283
|
pop.appendChild(inp);
|
|
258
284
|
} else if (type === 'enum') {
|
|
259
|
-
pop.appendChild(filterTitle(
|
|
285
|
+
pop.appendChild(filterTitle(textOf(root, 'filterEnum')));
|
|
260
286
|
var list = document.createElement('div');
|
|
261
287
|
list.className = 'fdy-filter__list';
|
|
262
288
|
var set = f.set || new Set();
|
|
@@ -279,7 +305,7 @@
|
|
|
279
305
|
});
|
|
280
306
|
pop.appendChild(list);
|
|
281
307
|
} else if (type === 'number') {
|
|
282
|
-
pop.appendChild(filterTitle(
|
|
308
|
+
pop.appendChild(filterTitle(textOf(root, 'filterRange')));
|
|
283
309
|
var range = document.createElement('div');
|
|
284
310
|
range.className = 'fdy-filter__range';
|
|
285
311
|
var minI = numberInput('Min', f.min);
|
|
@@ -304,7 +330,7 @@
|
|
|
304
330
|
var reset = document.createElement('button');
|
|
305
331
|
reset.type = 'button';
|
|
306
332
|
reset.className = 'fdy-btn fdy-btn--ghost fdy-btn--sm';
|
|
307
|
-
reset.textContent = '
|
|
333
|
+
reset.textContent = textOf(root, 'reset');
|
|
308
334
|
reset.addEventListener('click', function () {
|
|
309
335
|
delete colFilters[idx];
|
|
310
336
|
markActive(btn, false);
|
|
@@ -314,7 +340,7 @@
|
|
|
314
340
|
var done = document.createElement('button');
|
|
315
341
|
done.type = 'button';
|
|
316
342
|
done.className = 'fdy-btn fdy-btn--sm';
|
|
317
|
-
done.textContent = '
|
|
343
|
+
done.textContent = textOf(root, 'close');
|
|
318
344
|
done.addEventListener('click', function () { closePopover(true); });
|
|
319
345
|
foot.appendChild(reset); foot.appendChild(done);
|
|
320
346
|
pop.appendChild(foot);
|
|
@@ -339,7 +365,7 @@
|
|
|
339
365
|
btn.className = 'fdy-table__filterbtn';
|
|
340
366
|
btn.setAttribute('aria-haspopup', 'dialog');
|
|
341
367
|
btn.setAttribute('aria-pressed', 'false');
|
|
342
|
-
btn.setAttribute('aria-label',
|
|
368
|
+
btn.setAttribute('aria-label', textOf(root, 'filter'));
|
|
343
369
|
btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 5h18l-7 8v5l-4 2v-7z"></path></svg>';
|
|
344
370
|
btn.addEventListener('click', function (e) {
|
|
345
371
|
e.stopPropagation();
|
|
@@ -370,8 +396,8 @@
|
|
|
370
396
|
|
|
371
397
|
var shownFrom = total === 0 ? 0 : start + 1;
|
|
372
398
|
var shownTo = start + slice.length;
|
|
373
|
-
if (countEl) countEl.textContent =
|
|
374
|
-
if (infoEl) infoEl.textContent =
|
|
399
|
+
if (countEl) countEl.textContent = textOf(root, 'rows', { n: total });
|
|
400
|
+
if (infoEl) infoEl.textContent = textOf(root, 'info', { from: shownFrom, to: shownTo, total: total });
|
|
375
401
|
buildPager(totalPages);
|
|
376
402
|
syncSelectAll(slice);
|
|
377
403
|
updateBulk();
|
package/dist/freeday-toast.js
CHANGED
|
@@ -48,6 +48,21 @@
|
|
|
48
48
|
setTimeout(finish, 400); // fallback if transitionend never fires
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
|
|
52
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
53
|
+
* path — and every one overridable per element, so a host that speaks another language (the
|
|
54
|
+
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
55
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
56
|
+
var TEXT = {
|
|
57
|
+
close: 'Tutup'
|
|
58
|
+
};
|
|
59
|
+
function textOf(root, key, vars) {
|
|
60
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
61
|
+
var s = custom != null && custom !== '' ? custom : TEXT[key];
|
|
62
|
+
if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
|
|
63
|
+
return s;
|
|
64
|
+
}
|
|
65
|
+
|
|
51
66
|
function el(tag, className, text) {
|
|
52
67
|
var node = document.createElement(tag);
|
|
53
68
|
if (className) node.className = className;
|
|
@@ -69,7 +84,7 @@
|
|
|
69
84
|
|
|
70
85
|
var close = el('button', 'fdy-toast__close');
|
|
71
86
|
close.type = 'button';
|
|
72
|
-
close.setAttribute('aria-label',
|
|
87
|
+
close.setAttribute('aria-label', opts.closeLabel || TEXT.close);
|
|
73
88
|
close.innerHTML = '×';
|
|
74
89
|
close.addEventListener('click', function () { dismiss(node); });
|
|
75
90
|
|
package/dist/freeday-upload.js
CHANGED
|
@@ -50,6 +50,27 @@
|
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
|
|
54
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
55
|
+
* path — and every one overridable per element, so a host that speaks another language (the
|
|
56
|
+
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
57
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
58
|
+
var TEXT = {
|
|
59
|
+
remove: 'Hapus {name}',
|
|
60
|
+
progress: 'Progres unggah {name}',
|
|
61
|
+
uploading: 'Mengunggah…',
|
|
62
|
+
waiting: 'Menunggu server…',
|
|
63
|
+
done: 'Terunggah',
|
|
64
|
+
badType: 'Tipe berkas tidak didukung.',
|
|
65
|
+
tooBig: 'Ukuran melebihi batas ({max}).'
|
|
66
|
+
};
|
|
67
|
+
function textOf(root, key, vars) {
|
|
68
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
69
|
+
var s = custom != null && custom !== '' ? custom : TEXT[key];
|
|
70
|
+
if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
|
|
71
|
+
return s;
|
|
72
|
+
}
|
|
73
|
+
|
|
53
74
|
function makeRow(file, zone) {
|
|
54
75
|
var el = document.createElement('div');
|
|
55
76
|
el.className = 'fdy-file';
|
|
@@ -69,7 +90,7 @@
|
|
|
69
90
|
var remove = document.createElement('button');
|
|
70
91
|
remove.type = 'button';
|
|
71
92
|
remove.className = 'fdy-file__remove';
|
|
72
|
-
remove.setAttribute('aria-label',
|
|
93
|
+
remove.setAttribute('aria-label', textOf(zone, 'remove', { name: file.name }));
|
|
73
94
|
remove.innerHTML = '×';
|
|
74
95
|
remove.addEventListener('click', function () {
|
|
75
96
|
/* Dispatched on the ZONE, not on the row — the same target as fdy-upload-add, so one listener
|
|
@@ -96,7 +117,7 @@
|
|
|
96
117
|
progressEl.setAttribute('role', 'progressbar');
|
|
97
118
|
progressEl.setAttribute('aria-valuemin', '0');
|
|
98
119
|
progressEl.setAttribute('aria-valuemax', '100');
|
|
99
|
-
progressEl.setAttribute('aria-label', '
|
|
120
|
+
progressEl.setAttribute('aria-label', textOf(zone, 'progress', { name: file.name }));
|
|
100
121
|
bar = document.createElement('div');
|
|
101
122
|
bar.className = 'fdy-progress__bar';
|
|
102
123
|
bar.style.width = '0%';
|
|
@@ -129,7 +150,7 @@
|
|
|
129
150
|
uploading: function () {
|
|
130
151
|
el.classList.remove('fdy-file--error', 'fdy-file--success');
|
|
131
152
|
icon.innerHTML = FILE_ICON;
|
|
132
|
-
sub.textContent = fmtSize(file.size) + ' ·
|
|
153
|
+
sub.textContent = fmtSize(file.size) + ' · ' + textOf(zone, 'uploading');
|
|
133
154
|
ensureProgress();
|
|
134
155
|
determinate();
|
|
135
156
|
},
|
|
@@ -149,7 +170,7 @@
|
|
|
149
170
|
waiting: function (label) {
|
|
150
171
|
el.classList.remove('fdy-file--error', 'fdy-file--success');
|
|
151
172
|
icon.innerHTML = FILE_ICON;
|
|
152
|
-
sub.textContent = fmtSize(file.size) + ' · ' + (label ||
|
|
173
|
+
sub.textContent = fmtSize(file.size) + ' · ' + (label || textOf(zone, 'waiting'));
|
|
153
174
|
ensureProgress();
|
|
154
175
|
progressEl.classList.add('fdy-progress--indeterminate');
|
|
155
176
|
/* The modifier styles .fdy-progress__bar, so it must sit on the CONTAINER, and the inline
|
|
@@ -163,7 +184,7 @@
|
|
|
163
184
|
el.classList.add('fdy-file--success');
|
|
164
185
|
el.classList.remove('fdy-file--error');
|
|
165
186
|
icon.innerHTML = OK_ICON;
|
|
166
|
-
sub.textContent = fmtSize(file.size) + ' ·
|
|
187
|
+
sub.textContent = fmtSize(file.size) + ' · ' + textOf(zone, 'done');
|
|
167
188
|
dropProgress();
|
|
168
189
|
},
|
|
169
190
|
fail: function (msg) {
|
|
@@ -231,8 +252,8 @@
|
|
|
231
252
|
if (!fileList) return;
|
|
232
253
|
Array.prototype.slice.call(fileList).forEach(function (file) {
|
|
233
254
|
var reason = null;
|
|
234
|
-
if (!accepts(file, acceptAttr)) reason =
|
|
235
|
-
else if (maxSize && file.size > maxSize) reason = '
|
|
255
|
+
if (!accepts(file, acceptAttr)) reason = textOf(zone, 'badType');
|
|
256
|
+
else if (maxSize && file.size > maxSize) reason = textOf(zone, 'tooBig', { max: fmtSize(maxSize) });
|
|
236
257
|
var row = makeRow(file, zone);
|
|
237
258
|
if (list) list.appendChild(row.el);
|
|
238
259
|
if (reason) {
|
package/dist/freeday.bundle.css
CHANGED
|
@@ -1112,6 +1112,12 @@ a { color: var(--color-primary); }
|
|
|
1112
1112
|
/* Freeday — Date picker (input-styled trigger + calendar popover).
|
|
1113
1113
|
* Built by freeday-datepicker.js from an empty [data-fdy-datepicker] wrapper. */
|
|
1114
1114
|
.fdy-datepicker{position:relative;display:inline-block;width:100%;max-width:14rem;}
|
|
1115
|
+
/* Inside a labelled field, a picker fills the field like every other control.
|
|
1116
|
+
The 14rem/11rem caps above suit a picker standing on its own; in a form grid
|
|
1117
|
+
they leave the date box visibly short of the combo beside it — 224px against
|
|
1118
|
+
319px in a two-column layout — and the row reads as ragged. The field's own
|
|
1119
|
+
max-width is what should govern there. */
|
|
1120
|
+
.fdy-field>.fdy-datepicker,.fdy-field>.fdy-timepicker,.fdy-field>.fdy-daterange{max-width:none;}
|
|
1115
1121
|
.fdy-datepicker__trigger{display:inline-flex;align-items:center;gap:var(--space-2);width:100%;height:var(--control-h);padding:0 var(--space-3);text-align:left;cursor:pointer;font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
|
|
1116
1122
|
.fdy-datepicker__trigger:hover{border-color:var(--color-text-muted);}
|
|
1117
1123
|
.fdy-datepicker__trigger:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
|
package/dist/freeday.css
CHANGED
|
@@ -729,6 +729,12 @@ a { color: var(--color-primary); }
|
|
|
729
729
|
/* Freeday — Date picker (input-styled trigger + calendar popover).
|
|
730
730
|
* Built by freeday-datepicker.js from an empty [data-fdy-datepicker] wrapper. */
|
|
731
731
|
.fdy-datepicker{position:relative;display:inline-block;width:100%;max-width:14rem;}
|
|
732
|
+
/* Inside a labelled field, a picker fills the field like every other control.
|
|
733
|
+
The 14rem/11rem caps above suit a picker standing on its own; in a form grid
|
|
734
|
+
they leave the date box visibly short of the combo beside it — 224px against
|
|
735
|
+
319px in a two-column layout — and the row reads as ragged. The field's own
|
|
736
|
+
max-width is what should govern there. */
|
|
737
|
+
.fdy-field>.fdy-datepicker,.fdy-field>.fdy-timepicker,.fdy-field>.fdy-daterange{max-width:none;}
|
|
732
738
|
.fdy-datepicker__trigger{display:inline-flex;align-items:center;gap:var(--space-2);width:100%;height:var(--control-h);padding:0 var(--space-3);text-align:left;cursor:pointer;font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
|
|
733
739
|
.fdy-datepicker__trigger:hover{border-color:var(--color-text-muted);}
|
|
734
740
|
.fdy-datepicker__trigger:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
|
package/dist/freeday.js
CHANGED
|
@@ -210,6 +210,22 @@
|
|
|
210
210
|
(function () {
|
|
211
211
|
'use strict';
|
|
212
212
|
|
|
213
|
+
|
|
214
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
215
|
+
* path — and every one overridable per element, so a host that speaks another language (the
|
|
216
|
+
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
217
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
218
|
+
var TEXT = {
|
|
219
|
+
position: '{n} dari {total}',
|
|
220
|
+
slide: 'Slide {n}'
|
|
221
|
+
};
|
|
222
|
+
function textOf(root, key, vars) {
|
|
223
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
224
|
+
var s = custom != null && custom !== '' ? custom : TEXT[key];
|
|
225
|
+
if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
|
|
226
|
+
return s;
|
|
227
|
+
}
|
|
228
|
+
|
|
213
229
|
function initCarousel(root) {
|
|
214
230
|
if (root.dataset.fdyCarouselReady === '1') return;
|
|
215
231
|
root.dataset.fdyCarouselReady = '1';
|
|
@@ -223,7 +239,7 @@
|
|
|
223
239
|
var index = 0;
|
|
224
240
|
|
|
225
241
|
slides.forEach(function (s, i) {
|
|
226
|
-
if (!s.hasAttribute('aria-label')) s.setAttribute('aria-label', (
|
|
242
|
+
if (!s.hasAttribute('aria-label')) s.setAttribute('aria-label', textOf(root, 'position', { n: i + 1, total: slides.length }));
|
|
227
243
|
});
|
|
228
244
|
|
|
229
245
|
var dots = [];
|
|
@@ -232,7 +248,7 @@
|
|
|
232
248
|
var b = document.createElement('button');
|
|
233
249
|
b.type = 'button';
|
|
234
250
|
b.className = 'fdy-carousel__dot';
|
|
235
|
-
b.setAttribute('aria-label',
|
|
251
|
+
b.setAttribute('aria-label', textOf(root, 'slide', { n: i + 1 }));
|
|
236
252
|
b.addEventListener('click', function () { goTo(i); });
|
|
237
253
|
dotsWrap.appendChild(b);
|
|
238
254
|
dots.push(b);
|
|
@@ -361,6 +377,22 @@
|
|
|
361
377
|
return null;
|
|
362
378
|
}
|
|
363
379
|
|
|
380
|
+
|
|
381
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
382
|
+
* path — and every one overridable per element, so a host that speaks another language (the
|
|
383
|
+
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
384
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
385
|
+
var TEXT = {
|
|
386
|
+
back: 'Kembali satu tingkat',
|
|
387
|
+
submenu: '{label}, submenu'
|
|
388
|
+
};
|
|
389
|
+
function textOf(root, key, vars) {
|
|
390
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
391
|
+
var s = custom != null && custom !== '' ? custom : TEXT[key];
|
|
392
|
+
if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
|
|
393
|
+
return s;
|
|
394
|
+
}
|
|
395
|
+
|
|
364
396
|
function initCascade(wrap) {
|
|
365
397
|
if (wrap.dataset.fdyCascadeReady === '1') return;
|
|
366
398
|
wrap.dataset.fdyCascadeReady = '1';
|
|
@@ -392,7 +424,7 @@
|
|
|
392
424
|
var back = document.createElement('button');
|
|
393
425
|
back.type = 'button';
|
|
394
426
|
back.className = 'fdy-cascade__back';
|
|
395
|
-
back.setAttribute('aria-label',
|
|
427
|
+
back.setAttribute('aria-label', textOf(wrap, 'back'));
|
|
396
428
|
back.innerHTML = BACK;
|
|
397
429
|
back.hidden = true;
|
|
398
430
|
var crumb = document.createElement('span');
|
|
@@ -432,7 +464,7 @@
|
|
|
432
464
|
li.setAttribute('data-index', String(i));
|
|
433
465
|
var isBranch = !!node.children;
|
|
434
466
|
li.setAttribute('aria-selected', (!isBranch && node.value === selectedValue) ? 'true' : 'false');
|
|
435
|
-
if (isBranch) li.setAttribute('aria-label', node.label
|
|
467
|
+
if (isBranch) li.setAttribute('aria-label', textOf(wrap, 'submenu', { label: node.label }));
|
|
436
468
|
var lbl = document.createElement('span');
|
|
437
469
|
lbl.className = 'fdy-cascade__opt-label';
|
|
438
470
|
lbl.textContent = node.label;
|
|
@@ -601,6 +633,21 @@
|
|
|
601
633
|
.toLowerCase();
|
|
602
634
|
}
|
|
603
635
|
|
|
636
|
+
|
|
637
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
638
|
+
* path — and every one overridable per element, so a host that speaks another language (the
|
|
639
|
+
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
640
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
641
|
+
var TEXT = {
|
|
642
|
+
selected: '{n} dipilih'
|
|
643
|
+
};
|
|
644
|
+
function textOf(root, key, vars) {
|
|
645
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
646
|
+
var s = custom != null && custom !== '' ? custom : TEXT[key];
|
|
647
|
+
if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
|
|
648
|
+
return s;
|
|
649
|
+
}
|
|
650
|
+
|
|
604
651
|
function initDialog(dialog) {
|
|
605
652
|
if (dialog.dataset.fdyCflReady === '1') return;
|
|
606
653
|
dialog.dataset.fdyCflReady = '1';
|
|
@@ -638,7 +685,7 @@
|
|
|
638
685
|
function updateCount() {
|
|
639
686
|
if (!countEl) return;
|
|
640
687
|
var n = rows.filter(function (r) { return r.getAttribute('aria-selected') === 'true'; }).length;
|
|
641
|
-
countEl.textContent = n
|
|
688
|
+
countEl.textContent = textOf(dialog, 'selected', { n: n });
|
|
642
689
|
}
|
|
643
690
|
|
|
644
691
|
function fillGroup(detail) {
|
|
@@ -2030,7 +2077,12 @@
|
|
|
2030
2077
|
badInput: 'type',
|
|
2031
2078
|
customError: 'mismatch'
|
|
2032
2079
|
};
|
|
2033
|
-
|
|
2080
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
2081
|
+
* path — and overridable at three levels, narrowest first: `data-fdy-msg-<alias>` on the field,
|
|
2082
|
+
* `data-fdy-msg` on the field, then `data-fdy-text-<alias>` on the FORM. The form level is what
|
|
2083
|
+
* a host in another language needs: it sets nine messages once instead of on every input.
|
|
2084
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
2085
|
+
var TEXT = {
|
|
2034
2086
|
required: 'Wajib diisi.',
|
|
2035
2087
|
type: 'Format tidak valid.',
|
|
2036
2088
|
pattern: 'Format tidak sesuai.',
|
|
@@ -2039,8 +2091,13 @@
|
|
|
2039
2091
|
min: 'Nilai terlalu kecil.',
|
|
2040
2092
|
max: 'Nilai terlalu besar.',
|
|
2041
2093
|
step: 'Nilai tidak sesuai kelipatan.',
|
|
2042
|
-
mismatch: 'Nilai tidak cocok.'
|
|
2094
|
+
mismatch: 'Nilai tidak cocok.',
|
|
2095
|
+
invalid: 'Tidak valid.'
|
|
2043
2096
|
};
|
|
2097
|
+
function textOf(root, key) {
|
|
2098
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
2099
|
+
return custom != null && custom !== '' ? custom : TEXT[key];
|
|
2100
|
+
}
|
|
2044
2101
|
var VALIDITY_KEYS = ['valueMissing', 'typeMismatch', 'patternMismatch', 'tooShort', 'tooLong', 'rangeUnderflow', 'rangeOverflow', 'stepMismatch', 'badInput'];
|
|
2045
2102
|
|
|
2046
2103
|
function isCandidate(el) {
|
|
@@ -2074,7 +2131,7 @@
|
|
|
2074
2131
|
if (matchSel) {
|
|
2075
2132
|
var other = document.querySelector(matchSel);
|
|
2076
2133
|
if (other && el.value !== other.value) {
|
|
2077
|
-
el.setCustomValidity(el.getAttribute('data-fdy-msg-mismatch') || el.getAttribute('data-fdy-msg') ||
|
|
2134
|
+
el.setCustomValidity(el.getAttribute('data-fdy-msg-mismatch') || el.getAttribute('data-fdy-msg') || textOf(el.form || el.closest('form'), 'mismatch'));
|
|
2078
2135
|
}
|
|
2079
2136
|
}
|
|
2080
2137
|
if (el.validity.valid) return '';
|
|
@@ -2085,7 +2142,8 @@
|
|
|
2085
2142
|
}
|
|
2086
2143
|
}
|
|
2087
2144
|
var custom = alias ? el.getAttribute('data-fdy-msg-' + alias) : null;
|
|
2088
|
-
|
|
2145
|
+
var form = el.form || el.closest('form');
|
|
2146
|
+
return custom || el.getAttribute('data-fdy-msg') || (alias && textOf(form, alias)) || el.validationMessage || textOf(form, 'invalid');
|
|
2089
2147
|
}
|
|
2090
2148
|
|
|
2091
2149
|
function paint(el, message) {
|
|
@@ -2227,6 +2285,22 @@
|
|
|
2227
2285
|
return out;
|
|
2228
2286
|
}
|
|
2229
2287
|
|
|
2288
|
+
|
|
2289
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
2290
|
+
* path — and every one overridable per element, so a host that speaks another language (the
|
|
2291
|
+
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
2292
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
2293
|
+
var TEXT = {
|
|
2294
|
+
show: 'Tampilkan kata sandi',
|
|
2295
|
+
hide: 'Sembunyikan kata sandi'
|
|
2296
|
+
};
|
|
2297
|
+
function textOf(root, key, vars) {
|
|
2298
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
2299
|
+
var s = custom != null && custom !== '' ? custom : TEXT[key];
|
|
2300
|
+
if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
|
|
2301
|
+
return s;
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2230
2304
|
function initMask(el) {
|
|
2231
2305
|
if (el.dataset.fdyMaskReady === '1') return;
|
|
2232
2306
|
el.dataset.fdyMaskReady = '1';
|
|
@@ -2262,13 +2336,13 @@
|
|
|
2262
2336
|
btn.type = 'button';
|
|
2263
2337
|
btn.className = 'fdy-input-group__btn';
|
|
2264
2338
|
btn.setAttribute('aria-pressed', 'false');
|
|
2265
|
-
btn.setAttribute('aria-label',
|
|
2339
|
+
btn.setAttribute('aria-label', textOf(input, 'show'));
|
|
2266
2340
|
btn.innerHTML = EYE;
|
|
2267
2341
|
btn.addEventListener('click', function () {
|
|
2268
2342
|
var reveal = input.type === 'password';
|
|
2269
2343
|
input.type = reveal ? 'text' : 'password';
|
|
2270
2344
|
btn.setAttribute('aria-pressed', reveal ? 'true' : 'false');
|
|
2271
|
-
btn.setAttribute('aria-label', reveal ?
|
|
2345
|
+
btn.setAttribute('aria-label', reveal ? textOf(input, 'hide') : textOf(input, 'show'));
|
|
2272
2346
|
btn.innerHTML = reveal ? EYE_OFF : EYE;
|
|
2273
2347
|
input.focus();
|
|
2274
2348
|
});
|
|
@@ -2892,6 +2966,22 @@
|
|
|
2892
2966
|
|
|
2893
2967
|
var CHECK = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"></path></svg>';
|
|
2894
2968
|
|
|
2969
|
+
|
|
2970
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
2971
|
+
* path — and every one overridable per element, so a host that speaks another language (the
|
|
2972
|
+
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
2973
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
2974
|
+
var TEXT = {
|
|
2975
|
+
done: 'Selesai',
|
|
2976
|
+
next: 'Lanjut'
|
|
2977
|
+
};
|
|
2978
|
+
function textOf(root, key, vars) {
|
|
2979
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
2980
|
+
var s = custom != null && custom !== '' ? custom : TEXT[key];
|
|
2981
|
+
if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
|
|
2982
|
+
return s;
|
|
2983
|
+
}
|
|
2984
|
+
|
|
2895
2985
|
function initStepper(root) {
|
|
2896
2986
|
if (root.dataset.fdyStepperReady === '1') return;
|
|
2897
2987
|
root.dataset.fdyStepperReady = '1';
|
|
@@ -2926,7 +3016,7 @@
|
|
|
2926
3016
|
});
|
|
2927
3017
|
panels.forEach(function (p, i) { p.hidden = i !== active; });
|
|
2928
3018
|
if (prevBtn) prevBtn.disabled = active === 0;
|
|
2929
|
-
if (nextBtn) nextBtn.textContent = active === steps.length - 1 ? '
|
|
3019
|
+
if (nextBtn) nextBtn.textContent = active === steps.length - 1 ? textOf(root, 'done') : (nextLabel || textOf(root, 'next'));
|
|
2930
3020
|
root.dispatchEvent(new CustomEvent('fdy-step-change', { bubbles: true, detail: { index: active } }));
|
|
2931
3021
|
}
|
|
2932
3022
|
|
|
@@ -3001,6 +3091,32 @@
|
|
|
3001
3091
|
: cell.textContent).trim();
|
|
3002
3092
|
}
|
|
3003
3093
|
|
|
3094
|
+
|
|
3095
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
3096
|
+
* path — and every one overridable per element, so a host that speaks another language (the
|
|
3097
|
+
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
3098
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
3099
|
+
var TEXT = {
|
|
3100
|
+
prev: 'Sebelumnya',
|
|
3101
|
+
next: 'Berikutnya',
|
|
3102
|
+
selected: '{n} dipilih',
|
|
3103
|
+
filter: 'Filter kolom',
|
|
3104
|
+
filterText: 'Berisi teks',
|
|
3105
|
+
filterTextPlaceholder: 'Berisi…',
|
|
3106
|
+
filterEnum: 'Tampilkan nilai',
|
|
3107
|
+
filterRange: 'Rentang nilai',
|
|
3108
|
+
reset: 'Reset',
|
|
3109
|
+
close: 'Tutup',
|
|
3110
|
+
rows: '{n} baris',
|
|
3111
|
+
info: 'Menampilkan {from}–{to} dari {total}'
|
|
3112
|
+
};
|
|
3113
|
+
function textOf(root, key, vars) {
|
|
3114
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
3115
|
+
var s = custom != null && custom !== '' ? custom : TEXT[key];
|
|
3116
|
+
if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
|
|
3117
|
+
return s;
|
|
3118
|
+
}
|
|
3119
|
+
|
|
3004
3120
|
function initTable(root) {
|
|
3005
3121
|
if (root.dataset.fdyTableReady === '1') return;
|
|
3006
3122
|
var table = root.querySelector('table.fdy-table');
|
|
@@ -3125,7 +3241,7 @@
|
|
|
3125
3241
|
ul.appendChild(li);
|
|
3126
3242
|
}
|
|
3127
3243
|
|
|
3128
|
-
addBtn('‹', page - 1, { disabled: page === 1, label: '
|
|
3244
|
+
addBtn('‹', page - 1, { disabled: page === 1, label: textOf(root, 'prev') });
|
|
3129
3245
|
var pages = [];
|
|
3130
3246
|
for (var p = 1; p <= totalPages; p++) {
|
|
3131
3247
|
if (p === 1 || p === totalPages || (p >= page - 1 && p <= page + 1)) pages.push(p);
|
|
@@ -3135,7 +3251,7 @@
|
|
|
3135
3251
|
if (p === '…') addEllipsis();
|
|
3136
3252
|
else addBtn(String(p), p, { current: p === page });
|
|
3137
3253
|
});
|
|
3138
|
-
addBtn('›', page + 1, { disabled: page === totalPages, label: '
|
|
3254
|
+
addBtn('›', page + 1, { disabled: page === totalPages, label: textOf(root, 'next') });
|
|
3139
3255
|
pagerEl.appendChild(ul);
|
|
3140
3256
|
}
|
|
3141
3257
|
|
|
@@ -3150,7 +3266,7 @@
|
|
|
3150
3266
|
if (!bulkBar) return;
|
|
3151
3267
|
var n = selectedRows().length;
|
|
3152
3268
|
bulkBar.hidden = n === 0;
|
|
3153
|
-
if (bulkCount) bulkCount.textContent = n
|
|
3269
|
+
if (bulkCount) bulkCount.textContent = textOf(root, 'selected', { n: n });
|
|
3154
3270
|
}
|
|
3155
3271
|
function clearSelection() {
|
|
3156
3272
|
allRows.forEach(function (r) {
|
|
@@ -3206,15 +3322,15 @@
|
|
|
3206
3322
|
var pop = document.createElement('div');
|
|
3207
3323
|
pop.className = 'fdy-filter';
|
|
3208
3324
|
pop.setAttribute('role', 'dialog');
|
|
3209
|
-
pop.setAttribute('aria-label',
|
|
3325
|
+
pop.setAttribute('aria-label', textOf(root, 'filter'));
|
|
3210
3326
|
var f = colFilters[idx] || { type: type };
|
|
3211
3327
|
|
|
3212
3328
|
if (type === 'text') {
|
|
3213
|
-
pop.appendChild(filterTitle(
|
|
3329
|
+
pop.appendChild(filterTitle(textOf(root, 'filterText')));
|
|
3214
3330
|
var inp = document.createElement('input');
|
|
3215
3331
|
inp.className = 'fdy-input';
|
|
3216
3332
|
inp.type = 'search';
|
|
3217
|
-
inp.placeholder = '
|
|
3333
|
+
inp.placeholder = textOf(root, 'filterTextPlaceholder');
|
|
3218
3334
|
inp.value = f.text || '';
|
|
3219
3335
|
inp.addEventListener('input', function () {
|
|
3220
3336
|
f.text = inp.value.trim();
|
|
@@ -3224,7 +3340,7 @@
|
|
|
3224
3340
|
});
|
|
3225
3341
|
pop.appendChild(inp);
|
|
3226
3342
|
} else if (type === 'enum') {
|
|
3227
|
-
pop.appendChild(filterTitle(
|
|
3343
|
+
pop.appendChild(filterTitle(textOf(root, 'filterEnum')));
|
|
3228
3344
|
var list = document.createElement('div');
|
|
3229
3345
|
list.className = 'fdy-filter__list';
|
|
3230
3346
|
var set = f.set || new Set();
|
|
@@ -3247,7 +3363,7 @@
|
|
|
3247
3363
|
});
|
|
3248
3364
|
pop.appendChild(list);
|
|
3249
3365
|
} else if (type === 'number') {
|
|
3250
|
-
pop.appendChild(filterTitle(
|
|
3366
|
+
pop.appendChild(filterTitle(textOf(root, 'filterRange')));
|
|
3251
3367
|
var range = document.createElement('div');
|
|
3252
3368
|
range.className = 'fdy-filter__range';
|
|
3253
3369
|
var minI = numberInput('Min', f.min);
|
|
@@ -3272,7 +3388,7 @@
|
|
|
3272
3388
|
var reset = document.createElement('button');
|
|
3273
3389
|
reset.type = 'button';
|
|
3274
3390
|
reset.className = 'fdy-btn fdy-btn--ghost fdy-btn--sm';
|
|
3275
|
-
reset.textContent = '
|
|
3391
|
+
reset.textContent = textOf(root, 'reset');
|
|
3276
3392
|
reset.addEventListener('click', function () {
|
|
3277
3393
|
delete colFilters[idx];
|
|
3278
3394
|
markActive(btn, false);
|
|
@@ -3282,7 +3398,7 @@
|
|
|
3282
3398
|
var done = document.createElement('button');
|
|
3283
3399
|
done.type = 'button';
|
|
3284
3400
|
done.className = 'fdy-btn fdy-btn--sm';
|
|
3285
|
-
done.textContent = '
|
|
3401
|
+
done.textContent = textOf(root, 'close');
|
|
3286
3402
|
done.addEventListener('click', function () { closePopover(true); });
|
|
3287
3403
|
foot.appendChild(reset); foot.appendChild(done);
|
|
3288
3404
|
pop.appendChild(foot);
|
|
@@ -3307,7 +3423,7 @@
|
|
|
3307
3423
|
btn.className = 'fdy-table__filterbtn';
|
|
3308
3424
|
btn.setAttribute('aria-haspopup', 'dialog');
|
|
3309
3425
|
btn.setAttribute('aria-pressed', 'false');
|
|
3310
|
-
btn.setAttribute('aria-label',
|
|
3426
|
+
btn.setAttribute('aria-label', textOf(root, 'filter'));
|
|
3311
3427
|
btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 5h18l-7 8v5l-4 2v-7z"></path></svg>';
|
|
3312
3428
|
btn.addEventListener('click', function (e) {
|
|
3313
3429
|
e.stopPropagation();
|
|
@@ -3338,8 +3454,8 @@
|
|
|
3338
3454
|
|
|
3339
3455
|
var shownFrom = total === 0 ? 0 : start + 1;
|
|
3340
3456
|
var shownTo = start + slice.length;
|
|
3341
|
-
if (countEl) countEl.textContent =
|
|
3342
|
-
if (infoEl) infoEl.textContent =
|
|
3457
|
+
if (countEl) countEl.textContent = textOf(root, 'rows', { n: total });
|
|
3458
|
+
if (infoEl) infoEl.textContent = textOf(root, 'info', { from: shownFrom, to: shownTo, total: total });
|
|
3343
3459
|
buildPager(totalPages);
|
|
3344
3460
|
syncSelectAll(slice);
|
|
3345
3461
|
updateBulk();
|
|
@@ -3768,6 +3884,21 @@
|
|
|
3768
3884
|
setTimeout(finish, 400); // fallback if transitionend never fires
|
|
3769
3885
|
}
|
|
3770
3886
|
|
|
3887
|
+
|
|
3888
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
3889
|
+
* path — and every one overridable per element, so a host that speaks another language (the
|
|
3890
|
+
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
3891
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
3892
|
+
var TEXT = {
|
|
3893
|
+
close: 'Tutup'
|
|
3894
|
+
};
|
|
3895
|
+
function textOf(root, key, vars) {
|
|
3896
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
3897
|
+
var s = custom != null && custom !== '' ? custom : TEXT[key];
|
|
3898
|
+
if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
|
|
3899
|
+
return s;
|
|
3900
|
+
}
|
|
3901
|
+
|
|
3771
3902
|
function el(tag, className, text) {
|
|
3772
3903
|
var node = document.createElement(tag);
|
|
3773
3904
|
if (className) node.className = className;
|
|
@@ -3789,7 +3920,7 @@
|
|
|
3789
3920
|
|
|
3790
3921
|
var close = el('button', 'fdy-toast__close');
|
|
3791
3922
|
close.type = 'button';
|
|
3792
|
-
close.setAttribute('aria-label',
|
|
3923
|
+
close.setAttribute('aria-label', opts.closeLabel || TEXT.close);
|
|
3793
3924
|
close.innerHTML = '×';
|
|
3794
3925
|
close.addEventListener('click', function () { dismiss(node); });
|
|
3795
3926
|
|
|
@@ -3974,6 +4105,27 @@
|
|
|
3974
4105
|
});
|
|
3975
4106
|
}
|
|
3976
4107
|
|
|
4108
|
+
|
|
4109
|
+
/* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
|
|
4110
|
+
* path — and every one overridable per element, so a host that speaks another language (the
|
|
4111
|
+
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
4112
|
+
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
4113
|
+
var TEXT = {
|
|
4114
|
+
remove: 'Hapus {name}',
|
|
4115
|
+
progress: 'Progres unggah {name}',
|
|
4116
|
+
uploading: 'Mengunggah…',
|
|
4117
|
+
waiting: 'Menunggu server…',
|
|
4118
|
+
done: 'Terunggah',
|
|
4119
|
+
badType: 'Tipe berkas tidak didukung.',
|
|
4120
|
+
tooBig: 'Ukuran melebihi batas ({max}).'
|
|
4121
|
+
};
|
|
4122
|
+
function textOf(root, key, vars) {
|
|
4123
|
+
var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
|
|
4124
|
+
var s = custom != null && custom !== '' ? custom : TEXT[key];
|
|
4125
|
+
if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
|
|
4126
|
+
return s;
|
|
4127
|
+
}
|
|
4128
|
+
|
|
3977
4129
|
function makeRow(file, zone) {
|
|
3978
4130
|
var el = document.createElement('div');
|
|
3979
4131
|
el.className = 'fdy-file';
|
|
@@ -3993,7 +4145,7 @@
|
|
|
3993
4145
|
var remove = document.createElement('button');
|
|
3994
4146
|
remove.type = 'button';
|
|
3995
4147
|
remove.className = 'fdy-file__remove';
|
|
3996
|
-
remove.setAttribute('aria-label',
|
|
4148
|
+
remove.setAttribute('aria-label', textOf(zone, 'remove', { name: file.name }));
|
|
3997
4149
|
remove.innerHTML = '×';
|
|
3998
4150
|
remove.addEventListener('click', function () {
|
|
3999
4151
|
/* Dispatched on the ZONE, not on the row — the same target as fdy-upload-add, so one listener
|
|
@@ -4020,7 +4172,7 @@
|
|
|
4020
4172
|
progressEl.setAttribute('role', 'progressbar');
|
|
4021
4173
|
progressEl.setAttribute('aria-valuemin', '0');
|
|
4022
4174
|
progressEl.setAttribute('aria-valuemax', '100');
|
|
4023
|
-
progressEl.setAttribute('aria-label', '
|
|
4175
|
+
progressEl.setAttribute('aria-label', textOf(zone, 'progress', { name: file.name }));
|
|
4024
4176
|
bar = document.createElement('div');
|
|
4025
4177
|
bar.className = 'fdy-progress__bar';
|
|
4026
4178
|
bar.style.width = '0%';
|
|
@@ -4053,7 +4205,7 @@
|
|
|
4053
4205
|
uploading: function () {
|
|
4054
4206
|
el.classList.remove('fdy-file--error', 'fdy-file--success');
|
|
4055
4207
|
icon.innerHTML = FILE_ICON;
|
|
4056
|
-
sub.textContent = fmtSize(file.size) + ' ·
|
|
4208
|
+
sub.textContent = fmtSize(file.size) + ' · ' + textOf(zone, 'uploading');
|
|
4057
4209
|
ensureProgress();
|
|
4058
4210
|
determinate();
|
|
4059
4211
|
},
|
|
@@ -4073,7 +4225,7 @@
|
|
|
4073
4225
|
waiting: function (label) {
|
|
4074
4226
|
el.classList.remove('fdy-file--error', 'fdy-file--success');
|
|
4075
4227
|
icon.innerHTML = FILE_ICON;
|
|
4076
|
-
sub.textContent = fmtSize(file.size) + ' · ' + (label ||
|
|
4228
|
+
sub.textContent = fmtSize(file.size) + ' · ' + (label || textOf(zone, 'waiting'));
|
|
4077
4229
|
ensureProgress();
|
|
4078
4230
|
progressEl.classList.add('fdy-progress--indeterminate');
|
|
4079
4231
|
/* The modifier styles .fdy-progress__bar, so it must sit on the CONTAINER, and the inline
|
|
@@ -4087,7 +4239,7 @@
|
|
|
4087
4239
|
el.classList.add('fdy-file--success');
|
|
4088
4240
|
el.classList.remove('fdy-file--error');
|
|
4089
4241
|
icon.innerHTML = OK_ICON;
|
|
4090
|
-
sub.textContent = fmtSize(file.size) + ' ·
|
|
4242
|
+
sub.textContent = fmtSize(file.size) + ' · ' + textOf(zone, 'done');
|
|
4091
4243
|
dropProgress();
|
|
4092
4244
|
},
|
|
4093
4245
|
fail: function (msg) {
|
|
@@ -4155,8 +4307,8 @@
|
|
|
4155
4307
|
if (!fileList) return;
|
|
4156
4308
|
Array.prototype.slice.call(fileList).forEach(function (file) {
|
|
4157
4309
|
var reason = null;
|
|
4158
|
-
if (!accepts(file, acceptAttr)) reason =
|
|
4159
|
-
else if (maxSize && file.size > maxSize) reason = '
|
|
4310
|
+
if (!accepts(file, acceptAttr)) reason = textOf(zone, 'badType');
|
|
4311
|
+
else if (maxSize && file.size > maxSize) reason = textOf(zone, 'tooBig', { max: fmtSize(maxSize) });
|
|
4160
4312
|
var row = makeRow(file, zone);
|
|
4161
4313
|
if (list) list.appendChild(row.el);
|
|
4162
4314
|
if (reason) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cahyo-dimas/freeday",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.40.0",
|
|
4
4
|
"description": "Freeday — token-driven, framework-agnostic UI KIT (design source-of-truth).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"scripts": {
|
|
93
93
|
"build": "node tokens/build.mjs",
|
|
94
94
|
"test": "node --test",
|
|
95
|
-
"test:browser": "node --test browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs browser/upload-states.mjs browser/number.mjs browser/card-stretch.mjs",
|
|
95
|
+
"test:browser": "node --test browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs browser/upload-states.mjs browser/number.mjs browser/card-stretch.mjs browser/text-override.mjs",
|
|
96
96
|
"prepack": "node tokens/build.mjs",
|
|
97
97
|
"version": "node tokens/build.mjs && git add dist",
|
|
98
98
|
"typecheck:react": "tsc -p adapters/react/tsconfig.json --noEmit"
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/* Freeday — Date picker (input-styled trigger + calendar popover).
|
|
2
2
|
* Built by freeday-datepicker.js from an empty [data-fdy-datepicker] wrapper. */
|
|
3
3
|
.fdy-datepicker{position:relative;display:inline-block;width:100%;max-width:14rem;}
|
|
4
|
+
/* Inside a labelled field, a picker fills the field like every other control.
|
|
5
|
+
The 14rem/11rem caps above suit a picker standing on its own; in a form grid
|
|
6
|
+
they leave the date box visibly short of the combo beside it — 224px against
|
|
7
|
+
319px in a two-column layout — and the row reads as ragged. The field's own
|
|
8
|
+
max-width is what should govern there. */
|
|
9
|
+
.fdy-field>.fdy-datepicker,.fdy-field>.fdy-timepicker,.fdy-field>.fdy-daterange{max-width:none;}
|
|
4
10
|
.fdy-datepicker__trigger{display:inline-flex;align-items:center;gap:var(--space-2);width:100%;height:var(--control-h);padding:0 var(--space-3);text-align:left;cursor:pointer;font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
|
|
5
11
|
.fdy-datepicker__trigger:hover{border-color:var(--color-text-muted);}
|
|
6
12
|
.fdy-datepicker__trigger:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
|