@cahyo-dimas/freeday 1.43.0 → 1.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,72 @@
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.46.0] — 2026-08-19
7
+ The docs site catches up with the components, and writing it out as a consumer found a defect in
8
+ #016's own contract.
9
+ ### Fixed
10
+ - **`data-fdy-text-*` could not be written the way HTML allows** (#023). The keys in each enhancer's
11
+ `TEXT` table are camelCase, and `textOf` looked up `'data-fdy-text-' + key` — but **HTML lowercases
12
+ attribute names**, so `data-fdy-text-filterText` becomes `...filtertext` while
13
+ `data-fdy-text-filter-text`, the spelling anybody would actually reach for, is a DIFFERENT
14
+ attribute the enhancer never read. It failed silently, which is the only way an override can fail.
15
+ The key is kebab-cased for the lookup now; the run-together spelling still resolves, so markup
16
+ written against 1.39.0 keeps working. Nine enhancers.
17
+ It survived #016's own guard because all seven overrides that guard asserts use **single-word**
18
+ keys, which have no case to lose — the guard was not wrong, it was unrepresentative. The spec now
19
+ overrides `filterText`, reached by clicking a column filter open. Mutation-tested.
20
+ ### Docs
21
+ - **`docs/index.html` was nine releases behind** — it stated v1.34.0 and demoed none of what landed
22
+ after it. Brought level with the components, as live demos rather than a feature list, because
23
+ that is what the rest of the page is:
24
+ - **New section — Tabel beku.** `.fdy-table--sticky` / `--sticky-head`, `.fdy-table__freeze` +
25
+ `--fdy-freeze-left`, `.fdy-table-scroll--frozen` + `--fdy-table-frozen-h` (1.35.0, 1.37.0), with
26
+ a rate matrix you can actually scroll on both axes, and the reason the two axes are separate
27
+ modifiers.
28
+ - **New section — Teks enhancer (i18n).** The `data-fdy-text-*` contract (1.39.0), demoed as the
29
+ same data table rendered entirely in English — the demo that found #023.
30
+ - **Badge tone scale** `--tone-1`…`--tone-8` (1.44.0), with the distinction the note drew: semantic
31
+ variants state a judgement, tones distinguish without implying one.
32
+ - **Date picker** now states the days → months → years drill (1.36.0); **Choose-from-list** states
33
+ `multiple` for the typed wrappers, not only the enhancer's `data-fdy-cfl-multiple` (1.42.0).
34
+ - Verified in a browser rather than by reading the markup: the i18n demo renders `Showing 1–3 of 5`
35
+ and a `Previous` pager, and the frozen table computes `border-collapse:separate` with
36
+ `position:sticky` cells.
37
+
38
+ ## [1.45.0] — 2026-08-19
39
+ Two defects in 1.42.0's multi-select CFL, found by the app that asked for it (IDU_EMATE_APPL_WEB, #022).
40
+ ### Fixed
41
+ - **The row tick rendered as the browser's checkbox, not the kit's** (#022), in all three adapters.
42
+ They shipped `class="fdy-check"` on a bare `<input>`; that class styles a wrapping `<label>` and
43
+ its `input` DESCENDANT, so on the input itself it applied `inline-flex` and a cursor and nothing
44
+ else — a raw UA control in the middle of a styled dialog. `COMPONENTS.md` already said which class
45
+ a bare input in a table takes: `.fdy-checkbox`. Every behavioural assertion in `browser/cfl-multi`
46
+ passed while it was wrong, because none of them looked at the rendering.
47
+ - **`aria-selected` broke `vue-tsc` in consuming apps** (#022). `String(isPicked(row))` widens to
48
+ `string`, and Vue types the attribute as `Booleanish` — so a strict app type-checking the kit's own
49
+ `.vue` source failed on a file it does not own. Now `'true' : 'false'`.
50
+ ### Added — guards
51
+ - `browser/cfl-multi.mjs` asserts the tick's computed `appearance` is `none`, which is the tell that
52
+ the kit painted the box rather than the UA. Mutation-tested.
53
+
54
+ ## [1.44.0] — 2026-08-19
55
+ One note from the back-office app (IDU_EMATE_APPL_WEB, #021).
56
+ ### Added
57
+ - **`--tone-1`…`--tone-8` on `.fdy-badge`** — a status vocabulary larger than the semantic palette.
58
+ `.fdy-avatar--tone-*` and `.fdy-chip--tone-*` already carried the categorical scale; the badge,
59
+ which is the component that actually renders status, did not. The reporting app's document lists
60
+ carry **ten distinct statuses in one column** — Draft, Submitted, Approved, Completed, Settled,
61
+ Closed, Transferred, InDeclaration, Open, Rejected — against five modifiers, so Approved, Closed
62
+ and Completed came out the same green and Draft, InDeclaration and Transferred the same grey.
63
+ Semantics still come first: a state that IS good, bad or waiting takes `--success` / `--danger` /
64
+ `--warning`, and these are for the rest. Same `--tone-*` tokens and the same 18%/50% mix as the
65
+ other two, so the existing contrast test already measures them.
66
+ ### Added — guards
67
+ - `test/css.test.mjs` asserts all eight bind their own token (a gap silently reuses a colour) and
68
+ that the mix stays identical to the avatar's and the chip's — one contrast test covers the three,
69
+ so a badge that drifted to its own ratio would be gated by a test measuring something else.
70
+ `test/contrast.test.mjs` renamed from "avatar tones" to "categorical tones" to say so.
71
+
6
72
  ## [1.43.0] — 2026-08-19
7
73
  Three crowding reports from one settlement screen (IDU_EMATE_APPL_WEB, #020). All three measured in
8
74
  Chromium rather than read off the CSS — each is a layout outcome, invisible in the stylesheet.
package/COMPONENTS.md CHANGED
@@ -773,7 +773,9 @@ pagination. Needs `freeday-table.js`. Wrap the whole thing in `.fdy-datatable` +
773
773
  wrapper**.
774
774
  - **Overriding an enhancer's strings.** Each enhancer keeps its strings in one `TEXT` table and
775
775
  reads them through `textOf()`, so any of them can be replaced per element with
776
- **`data-fdy-text-<key>`** — no forking, and no rendering the nodes yourself:
776
+ **`data-fdy-text-<key>`**, the key kebab-cased — no forking, and no rendering the nodes yourself.
777
+ (HTML lowercases attribute names, so a camelCase key like `filterText` is written
778
+ `data-fdy-text-filter-text`; the run-together spelling still resolves.):
777
779
 
778
780
  ```html
779
781
  <div data-fdy-table
@@ -1106,6 +1108,12 @@ while it stays a real `<button>`/`<a>` with real keyboard semantics.
1106
1108
  Inline status pill: `--success` `--warning` `--danger` `--info` `--outline`. Never colour-only —
1107
1109
  the text carries the meaning.
1108
1110
 
1111
+ `--tone-1`…`--tone-8` for a status vocabulary larger than the semantic palette. Semantics first —
1112
+ a state that IS good, bad or waiting takes `--success` / `--danger` / `--warning`; these are for
1113
+ the rest, so two states a workflow distinguishes never share a look. Map each status to a FIXED
1114
+ tone, never to a hash of the string: a status is a closed set, and a hash reassigns colours the
1115
+ moment the set grows.
1116
+
1109
1117
  **Notification badge is the overlay variant**, not a separate component: wrap anything in
1110
1118
  `.fdy-badge-wrap` and add `.fdy-badge-ov` (`--primary` `--danger` `--accent` `--info` `--success`
1111
1119
  `--warning`, `--dot` for a bare dot, `--top-left` / `--bottom-right` / `--bottom-left` to move it).
@@ -82,7 +82,7 @@
82
82
  @if (Multiple)
83
83
  {
84
84
  <td class="fdy-cfl__check">
85
- <input class="fdy-check" type="checkbox" tabindex="-1" checked="@IsPicked(row)" aria-hidden="true" />
85
+ <input class="fdy-checkbox" type="checkbox" tabindex="-1" checked="@IsPicked(row)" aria-hidden="true" />
86
86
  </td>
87
87
  }
88
88
  @foreach (CflColumn<TRow> col in Columns)
@@ -435,7 +435,7 @@ export function FdyCfl<Row extends Record<string, unknown>>(props: FdyCflProps<R
435
435
  >
436
436
  {isMultiple ? (
437
437
  <td className="fdy-cfl__check">
438
- <input className="fdy-check" type="checkbox" tabIndex={-1} checked={isPicked(row)} readOnly aria-hidden="true" />
438
+ <input className="fdy-checkbox" type="checkbox" tabIndex={-1} checked={isPicked(row)} readOnly aria-hidden="true" />
439
439
  </td>
440
440
  ) : null}
441
441
  {props.columns.map((col: CflColumn<Row>): JSX.Element => (
@@ -426,12 +426,12 @@ onBeforeUnmount((): void => {
426
426
  :key="rowKey(row)"
427
427
  class="fdy-cfl__row"
428
428
  :class="{ 'is-active': i === activeIndex }"
429
- :aria-selected="multiple === true ? String(isPicked(row)) : i === activeIndex ? 'true' : undefined"
429
+ :aria-selected="multiple === true ? (isPicked(row) ? 'true' : 'false') : i === activeIndex ? 'true' : undefined"
430
430
  @click="onRowClick(row)"
431
431
  @mousemove="setActive(i)"
432
432
  >
433
433
  <td v-if="multiple === true" class="fdy-cfl__check">
434
- <input class="fdy-check" type="checkbox" tabindex="-1" :checked="isPicked(row)" aria-hidden="true" />
434
+ <input class="fdy-checkbox" type="checkbox" tabindex="-1" :checked="isPicked(row)" aria-hidden="true" />
435
435
  </td>
436
436
  <td v-for="col in columns" :key="col.key">{{ cellText(row, col.key) }}</td>
437
437
  </tr>
@@ -26,8 +26,18 @@
26
26
  position: '{n} dari {total}',
27
27
  slide: 'Slide {n}'
28
28
  };
29
+ /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
30
+ as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
31
+ `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
32
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
33
+ still resolves, for markup written against 1.39.0. */
34
+ function textAttr(root, key) {
35
+ if (!root || !root.getAttribute) return null;
36
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
37
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
38
+ }
29
39
  function textOf(root, key, vars) {
30
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
40
+ var custom = textAttr(root, key);
31
41
  var s = custom != null && custom !== '' ? custom : TEXT[key];
32
42
  if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
33
43
  return s;
@@ -62,8 +62,18 @@
62
62
  back: 'Kembali satu tingkat',
63
63
  submenu: '{label}, submenu'
64
64
  };
65
+ /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
66
+ as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
67
+ `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
68
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
69
+ still resolves, for markup written against 1.39.0. */
70
+ function textAttr(root, key) {
71
+ if (!root || !root.getAttribute) return null;
72
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
73
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
74
+ }
65
75
  function textOf(root, key, vars) {
66
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
76
+ var custom = textAttr(root, key);
67
77
  var s = custom != null && custom !== '' ? custom : TEXT[key];
68
78
  if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
69
79
  return s;
@@ -41,8 +41,18 @@
41
41
  var TEXT = {
42
42
  selected: '{n} dipilih'
43
43
  };
44
+ /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
45
+ as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
46
+ `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
47
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
48
+ still resolves, for markup written against 1.39.0. */
49
+ function textAttr(root, key) {
50
+ if (!root || !root.getAttribute) return null;
51
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
52
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
53
+ }
44
54
  function textOf(root, key, vars) {
45
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
55
+ var custom = textAttr(root, key);
46
56
  var s = custom != null && custom !== '' ? custom : TEXT[key];
47
57
  if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
48
58
  return s;
@@ -55,8 +55,13 @@
55
55
  mismatch: 'Nilai tidak cocok.',
56
56
  invalid: 'Tidak valid.'
57
57
  };
58
+ /* Kebab-cased for the lookup — see the note in the other enhancers: HTML lowercases attribute
59
+ names, so `data-fdy-text-filter-text` and `data-fdy-text-filtertext` are different attributes
60
+ and only one of them is what an author would write. */
58
61
  function textOf(root, key) {
59
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
62
+ if (!root || !root.getAttribute) return TEXT[key];
63
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
64
+ var custom = kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
60
65
  return custom != null && custom !== '' ? custom : TEXT[key];
61
66
  }
62
67
  var VALIDITY_KEYS = ['valueMissing', 'typeMismatch', 'patternMismatch', 'tooShort', 'tooLong', 'rangeUnderflow', 'rangeOverflow', 'stepMismatch', 'badInput'];
@@ -59,8 +59,18 @@
59
59
  show: 'Tampilkan kata sandi',
60
60
  hide: 'Sembunyikan kata sandi'
61
61
  };
62
+ /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
63
+ as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
64
+ `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
65
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
66
+ still resolves, for markup written against 1.39.0. */
67
+ function textAttr(root, key) {
68
+ if (!root || !root.getAttribute) return null;
69
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
70
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
71
+ }
62
72
  function textOf(root, key, vars) {
63
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
73
+ var custom = textAttr(root, key);
64
74
  var s = custom != null && custom !== '' ? custom : TEXT[key];
65
75
  if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
66
76
  return s;
@@ -27,8 +27,18 @@
27
27
  done: 'Selesai',
28
28
  next: 'Lanjut'
29
29
  };
30
+ /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
31
+ as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
32
+ `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
33
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
34
+ still resolves, for markup written against 1.39.0. */
35
+ function textAttr(root, key) {
36
+ if (!root || !root.getAttribute) return null;
37
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
38
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
39
+ }
30
40
  function textOf(root, key, vars) {
31
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
41
+ var custom = textAttr(root, key);
32
42
  var s = custom != null && custom !== '' ? custom : TEXT[key];
33
43
  if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
34
44
  return s;
@@ -52,8 +52,18 @@
52
52
  rows: '{n} baris',
53
53
  info: 'Menampilkan {from}–{to} dari {total}'
54
54
  };
55
+ /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
56
+ as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
57
+ `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
58
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
59
+ still resolves, for markup written against 1.39.0. */
60
+ function textAttr(root, key) {
61
+ if (!root || !root.getAttribute) return null;
62
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
63
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
64
+ }
55
65
  function textOf(root, key, vars) {
56
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
66
+ var custom = textAttr(root, key);
57
67
  var s = custom != null && custom !== '' ? custom : TEXT[key];
58
68
  if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
59
69
  return s;
@@ -56,8 +56,18 @@
56
56
  var TEXT = {
57
57
  close: 'Tutup'
58
58
  };
59
+ /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
60
+ as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
61
+ `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
62
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
63
+ still resolves, for markup written against 1.39.0. */
64
+ function textAttr(root, key) {
65
+ if (!root || !root.getAttribute) return null;
66
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
67
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
68
+ }
59
69
  function textOf(root, key, vars) {
60
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
70
+ var custom = textAttr(root, key);
61
71
  var s = custom != null && custom !== '' ? custom : TEXT[key];
62
72
  if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
63
73
  return s;
@@ -64,8 +64,18 @@
64
64
  badType: 'Tipe berkas tidak didukung.',
65
65
  tooBig: 'Ukuran melebihi batas ({max}).'
66
66
  };
67
+ /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
68
+ as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
69
+ `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
70
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
71
+ still resolves, for markup written against 1.39.0. */
72
+ function textAttr(root, key) {
73
+ if (!root || !root.getAttribute) return null;
74
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
75
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
76
+ }
67
77
  function textOf(root, key, vars) {
68
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
78
+ var custom = textAttr(root, key);
69
79
  var s = custom != null && custom !== '' ? custom : TEXT[key];
70
80
  if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
71
81
  return s;
@@ -635,6 +635,25 @@ a { color: var(--color-primary); }
635
635
  .fdy-badge--info{color:var(--color-info-strong);background:var(--color-info-soft);border-color:color-mix(in srgb,var(--color-info) 26%,transparent);}
636
636
  .fdy-badge--outline{background:transparent;border-color:var(--color-border-strong);color:var(--color-text-muted);box-shadow:none;}
637
637
 
638
+ /* Categorical tone badges — a status vocabulary larger than the semantic palette, from the
639
+ * general --tone-1..8 tokens. Same validated AA formula as .fdy-avatar--tone-* and
640
+ * .fdy-chip--tone-* (text >=4.5:1 both themes, gated by test/contrast.test.mjs).
641
+ *
642
+ * Semantics come first: a state that IS good, bad or waiting takes --success / --danger /
643
+ * --warning. These are for the rest — the states a workflow distinguishes but a palette of five
644
+ * cannot. A back-office document list carrying Draft, Submitted, Approved, Completed, Settled,
645
+ * Closed, Transferred, InDeclaration, Open and Rejected in ONE column collapsed to three colours,
646
+ * which is the report this shipped for. */
647
+ .fdy-badge--tone-1,.fdy-badge--tone-2,.fdy-badge--tone-3,.fdy-badge--tone-4,.fdy-badge--tone-5,.fdy-badge--tone-6,.fdy-badge--tone-7,.fdy-badge--tone-8{background:color-mix(in srgb,var(--_fdy-badge-tone) 18%,var(--color-surface));color:color-mix(in srgb,var(--_fdy-badge-tone) 50%,var(--color-text));border-color:color-mix(in srgb,var(--_fdy-badge-tone) 26%,transparent);}
648
+ .fdy-badge--tone-1{--_fdy-badge-tone:var(--tone-1);}
649
+ .fdy-badge--tone-2{--_fdy-badge-tone:var(--tone-2);}
650
+ .fdy-badge--tone-3{--_fdy-badge-tone:var(--tone-3);}
651
+ .fdy-badge--tone-4{--_fdy-badge-tone:var(--tone-4);}
652
+ .fdy-badge--tone-5{--_fdy-badge-tone:var(--tone-5);}
653
+ .fdy-badge--tone-6{--_fdy-badge-tone:var(--tone-6);}
654
+ .fdy-badge--tone-7{--_fdy-badge-tone:var(--tone-7);}
655
+ .fdy-badge--tone-8{--_fdy-badge-tone:var(--tone-8);}
656
+
638
657
  /* Overlay badge — anchors a small count/dot/icon badge to a corner of any element
639
658
  * (icon, button, text). Wrap the content in .fdy-badge-wrap; add .fdy-badge-ov as a
640
659
  * sibling. Default is a solid neutral pill; colour + dot + position via modifiers. */
package/dist/freeday.css CHANGED
@@ -252,6 +252,25 @@ a { color: var(--color-primary); }
252
252
  .fdy-badge--info{color:var(--color-info-strong);background:var(--color-info-soft);border-color:color-mix(in srgb,var(--color-info) 26%,transparent);}
253
253
  .fdy-badge--outline{background:transparent;border-color:var(--color-border-strong);color:var(--color-text-muted);box-shadow:none;}
254
254
 
255
+ /* Categorical tone badges — a status vocabulary larger than the semantic palette, from the
256
+ * general --tone-1..8 tokens. Same validated AA formula as .fdy-avatar--tone-* and
257
+ * .fdy-chip--tone-* (text >=4.5:1 both themes, gated by test/contrast.test.mjs).
258
+ *
259
+ * Semantics come first: a state that IS good, bad or waiting takes --success / --danger /
260
+ * --warning. These are for the rest — the states a workflow distinguishes but a palette of five
261
+ * cannot. A back-office document list carrying Draft, Submitted, Approved, Completed, Settled,
262
+ * Closed, Transferred, InDeclaration, Open and Rejected in ONE column collapsed to three colours,
263
+ * which is the report this shipped for. */
264
+ .fdy-badge--tone-1,.fdy-badge--tone-2,.fdy-badge--tone-3,.fdy-badge--tone-4,.fdy-badge--tone-5,.fdy-badge--tone-6,.fdy-badge--tone-7,.fdy-badge--tone-8{background:color-mix(in srgb,var(--_fdy-badge-tone) 18%,var(--color-surface));color:color-mix(in srgb,var(--_fdy-badge-tone) 50%,var(--color-text));border-color:color-mix(in srgb,var(--_fdy-badge-tone) 26%,transparent);}
265
+ .fdy-badge--tone-1{--_fdy-badge-tone:var(--tone-1);}
266
+ .fdy-badge--tone-2{--_fdy-badge-tone:var(--tone-2);}
267
+ .fdy-badge--tone-3{--_fdy-badge-tone:var(--tone-3);}
268
+ .fdy-badge--tone-4{--_fdy-badge-tone:var(--tone-4);}
269
+ .fdy-badge--tone-5{--_fdy-badge-tone:var(--tone-5);}
270
+ .fdy-badge--tone-6{--_fdy-badge-tone:var(--tone-6);}
271
+ .fdy-badge--tone-7{--_fdy-badge-tone:var(--tone-7);}
272
+ .fdy-badge--tone-8{--_fdy-badge-tone:var(--tone-8);}
273
+
255
274
  /* Overlay badge — anchors a small count/dot/icon badge to a corner of any element
256
275
  * (icon, button, text). Wrap the content in .fdy-badge-wrap; add .fdy-badge-ov as a
257
276
  * sibling. Default is a solid neutral pill; colour + dot + position via modifiers. */
package/dist/freeday.js CHANGED
@@ -219,8 +219,18 @@
219
219
  position: '{n} dari {total}',
220
220
  slide: 'Slide {n}'
221
221
  };
222
+ /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
223
+ as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
224
+ `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
225
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
226
+ still resolves, for markup written against 1.39.0. */
227
+ function textAttr(root, key) {
228
+ if (!root || !root.getAttribute) return null;
229
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
230
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
231
+ }
222
232
  function textOf(root, key, vars) {
223
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
233
+ var custom = textAttr(root, key);
224
234
  var s = custom != null && custom !== '' ? custom : TEXT[key];
225
235
  if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
226
236
  return s;
@@ -386,8 +396,18 @@
386
396
  back: 'Kembali satu tingkat',
387
397
  submenu: '{label}, submenu'
388
398
  };
399
+ /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
400
+ as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
401
+ `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
402
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
403
+ still resolves, for markup written against 1.39.0. */
404
+ function textAttr(root, key) {
405
+ if (!root || !root.getAttribute) return null;
406
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
407
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
408
+ }
389
409
  function textOf(root, key, vars) {
390
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
410
+ var custom = textAttr(root, key);
391
411
  var s = custom != null && custom !== '' ? custom : TEXT[key];
392
412
  if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
393
413
  return s;
@@ -641,8 +661,18 @@
641
661
  var TEXT = {
642
662
  selected: '{n} dipilih'
643
663
  };
664
+ /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
665
+ as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
666
+ `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
667
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
668
+ still resolves, for markup written against 1.39.0. */
669
+ function textAttr(root, key) {
670
+ if (!root || !root.getAttribute) return null;
671
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
672
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
673
+ }
644
674
  function textOf(root, key, vars) {
645
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
675
+ var custom = textAttr(root, key);
646
676
  var s = custom != null && custom !== '' ? custom : TEXT[key];
647
677
  if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
648
678
  return s;
@@ -2094,8 +2124,13 @@
2094
2124
  mismatch: 'Nilai tidak cocok.',
2095
2125
  invalid: 'Tidak valid.'
2096
2126
  };
2127
+ /* Kebab-cased for the lookup — see the note in the other enhancers: HTML lowercases attribute
2128
+ names, so `data-fdy-text-filter-text` and `data-fdy-text-filtertext` are different attributes
2129
+ and only one of them is what an author would write. */
2097
2130
  function textOf(root, key) {
2098
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
2131
+ if (!root || !root.getAttribute) return TEXT[key];
2132
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
2133
+ var custom = kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
2099
2134
  return custom != null && custom !== '' ? custom : TEXT[key];
2100
2135
  }
2101
2136
  var VALIDITY_KEYS = ['valueMissing', 'typeMismatch', 'patternMismatch', 'tooShort', 'tooLong', 'rangeUnderflow', 'rangeOverflow', 'stepMismatch', 'badInput'];
@@ -2294,8 +2329,18 @@
2294
2329
  show: 'Tampilkan kata sandi',
2295
2330
  hide: 'Sembunyikan kata sandi'
2296
2331
  };
2332
+ /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
2333
+ as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
2334
+ `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
2335
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
2336
+ still resolves, for markup written against 1.39.0. */
2337
+ function textAttr(root, key) {
2338
+ if (!root || !root.getAttribute) return null;
2339
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
2340
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
2341
+ }
2297
2342
  function textOf(root, key, vars) {
2298
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
2343
+ var custom = textAttr(root, key);
2299
2344
  var s = custom != null && custom !== '' ? custom : TEXT[key];
2300
2345
  if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
2301
2346
  return s;
@@ -2975,8 +3020,18 @@
2975
3020
  done: 'Selesai',
2976
3021
  next: 'Lanjut'
2977
3022
  };
3023
+ /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
3024
+ as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
3025
+ `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
3026
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
3027
+ still resolves, for markup written against 1.39.0. */
3028
+ function textAttr(root, key) {
3029
+ if (!root || !root.getAttribute) return null;
3030
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
3031
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
3032
+ }
2978
3033
  function textOf(root, key, vars) {
2979
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
3034
+ var custom = textAttr(root, key);
2980
3035
  var s = custom != null && custom !== '' ? custom : TEXT[key];
2981
3036
  if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
2982
3037
  return s;
@@ -3110,8 +3165,18 @@
3110
3165
  rows: '{n} baris',
3111
3166
  info: 'Menampilkan {from}–{to} dari {total}'
3112
3167
  };
3168
+ /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
3169
+ as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
3170
+ `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
3171
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
3172
+ still resolves, for markup written against 1.39.0. */
3173
+ function textAttr(root, key) {
3174
+ if (!root || !root.getAttribute) return null;
3175
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
3176
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
3177
+ }
3113
3178
  function textOf(root, key, vars) {
3114
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
3179
+ var custom = textAttr(root, key);
3115
3180
  var s = custom != null && custom !== '' ? custom : TEXT[key];
3116
3181
  if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
3117
3182
  return s;
@@ -3892,8 +3957,18 @@
3892
3957
  var TEXT = {
3893
3958
  close: 'Tutup'
3894
3959
  };
3960
+ /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
3961
+ as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
3962
+ `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
3963
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
3964
+ still resolves, for markup written against 1.39.0. */
3965
+ function textAttr(root, key) {
3966
+ if (!root || !root.getAttribute) return null;
3967
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
3968
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
3969
+ }
3895
3970
  function textOf(root, key, vars) {
3896
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
3971
+ var custom = textAttr(root, key);
3897
3972
  var s = custom != null && custom !== '' ? custom : TEXT[key];
3898
3973
  if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
3899
3974
  return s;
@@ -4119,8 +4194,18 @@
4119
4194
  badType: 'Tipe berkas tidak didukung.',
4120
4195
  tooBig: 'Ukuran melebihi batas ({max}).'
4121
4196
  };
4197
+ /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
4198
+ as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
4199
+ `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
4200
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
4201
+ still resolves, for markup written against 1.39.0. */
4202
+ function textAttr(root, key) {
4203
+ if (!root || !root.getAttribute) return null;
4204
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
4205
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
4206
+ }
4122
4207
  function textOf(root, key, vars) {
4123
- var custom = root && root.getAttribute ? root.getAttribute('data-fdy-text-' + key) : null;
4208
+ var custom = textAttr(root, key);
4124
4209
  var s = custom != null && custom !== '' ? custom : TEXT[key];
4125
4210
  if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
4126
4211
  return s;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cahyo-dimas/freeday",
3
- "version": "1.43.0",
3
+ "version": "1.46.0",
4
4
  "description": "Freeday — token-driven, framework-agnostic UI KIT (design source-of-truth).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -6,6 +6,25 @@
6
6
  .fdy-badge--info{color:var(--color-info-strong);background:var(--color-info-soft);border-color:color-mix(in srgb,var(--color-info) 26%,transparent);}
7
7
  .fdy-badge--outline{background:transparent;border-color:var(--color-border-strong);color:var(--color-text-muted);box-shadow:none;}
8
8
 
9
+ /* Categorical tone badges — a status vocabulary larger than the semantic palette, from the
10
+ * general --tone-1..8 tokens. Same validated AA formula as .fdy-avatar--tone-* and
11
+ * .fdy-chip--tone-* (text >=4.5:1 both themes, gated by test/contrast.test.mjs).
12
+ *
13
+ * Semantics come first: a state that IS good, bad or waiting takes --success / --danger /
14
+ * --warning. These are for the rest — the states a workflow distinguishes but a palette of five
15
+ * cannot. A back-office document list carrying Draft, Submitted, Approved, Completed, Settled,
16
+ * Closed, Transferred, InDeclaration, Open and Rejected in ONE column collapsed to three colours,
17
+ * which is the report this shipped for. */
18
+ .fdy-badge--tone-1,.fdy-badge--tone-2,.fdy-badge--tone-3,.fdy-badge--tone-4,.fdy-badge--tone-5,.fdy-badge--tone-6,.fdy-badge--tone-7,.fdy-badge--tone-8{background:color-mix(in srgb,var(--_fdy-badge-tone) 18%,var(--color-surface));color:color-mix(in srgb,var(--_fdy-badge-tone) 50%,var(--color-text));border-color:color-mix(in srgb,var(--_fdy-badge-tone) 26%,transparent);}
19
+ .fdy-badge--tone-1{--_fdy-badge-tone:var(--tone-1);}
20
+ .fdy-badge--tone-2{--_fdy-badge-tone:var(--tone-2);}
21
+ .fdy-badge--tone-3{--_fdy-badge-tone:var(--tone-3);}
22
+ .fdy-badge--tone-4{--_fdy-badge-tone:var(--tone-4);}
23
+ .fdy-badge--tone-5{--_fdy-badge-tone:var(--tone-5);}
24
+ .fdy-badge--tone-6{--_fdy-badge-tone:var(--tone-6);}
25
+ .fdy-badge--tone-7{--_fdy-badge-tone:var(--tone-7);}
26
+ .fdy-badge--tone-8{--_fdy-badge-tone:var(--tone-8);}
27
+
9
28
  /* Overlay badge — anchors a small count/dot/icon badge to a corner of any element
10
29
  * (icon, button, text). Wrap the content in .fdy-badge-wrap; add .fdy-badge-ov as a
11
30
  * sibling. Default is a solid neutral pill; colour + dot + position via modifiers. */