@cahyo-dimas/freeday 1.25.0 → 1.26.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,46 @@
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.26.0] — 2026-08-14
7
+ Improvement note #43, found while chasing a "the upload is stuck" report on a 626 KB PDF: the
8
+ transfer took about a second, the server then spent nearly a minute reading the document.
9
+ ### Added
10
+ - **`row.waiting(label)`** — the state between `setProgress` and `done`: the bytes are gone, the
11
+ server has not answered. The row's only long-running state was named after the *transfer*, so it
12
+ kept saying "Mengunggah…" for the whole minute of server-side work — and `setProgress(100)` made it
13
+ worse, because a full bar that then sits still is the most convincing "hung" signal a UI can
14
+ produce. There was no way out within the row's API: `done()` claims success, `fail()` claims an
15
+ error, `ready()` walks backwards. Consumers were rendering a second status line outside the row and
16
+ leaving the row to contradict it.
17
+ The bar goes **indeterminate** and drops `aria-valuenow` — a progressbar with no value is exactly
18
+ what ARIA calls indeterminate, which is the contract `COMPONENTS.md` already stated for
19
+ `.fdy-progress`. The label is the consumer's, because only they know what the server is doing
20
+ (`Membaca PDF…`, `Memindai…`); it falls back to `Menunggu server…`.
21
+ - `COMPONENTS.md` gains the state in the row table plus the sentence that would have saved the
22
+ round-trip: **if your request outlives the transfer, drive `waiting()`**.
23
+ ### Notes on the shape of the fix
24
+ - The report's patch would have shipped the symptom it set out to remove. It put the modifier on the
25
+ **bar** (`.fdy-progress--indeterminate` styles `.fdy-progress__bar`, so it belongs on the
26
+ container — on the bar it matches nothing) and then set an inline `width:100%`, which beats the
27
+ modifier's own width anyway. Both mistakes render a full, frozen bar. The note also hedged that
28
+ `.fdy-progress--indeterminate` might not exist; it has all along.
29
+ - **Leaving the state needs more care than entering it.** `.fdy-progress__bar` is a plain block div:
30
+ with no width it fills its track. So `uploading()`/`setProgress()` restore an explicit width when
31
+ they clear the modifier, or a retried row paints a *full* bar while meaning 0%. `done()`, `fail()`
32
+ and `ready()` need no counterpart — they drop the progress element outright, modifier and all
33
+ (contrary to the note, which expected a line in each).
34
+ - **No `.fdy-file--waiting` class.** `uploading` has none either; only `--success`/`--error` do,
35
+ because they carry colour. A documented class with no rule is markup that looks like it does
36
+ something.
37
+ - Under `prefers-reduced-motion: reduce` the kit's indeterminate treatment is a dimmed **full** bar
38
+ (no animation left to carry the meaning) — pre-existing behaviour for every indeterminate progress,
39
+ not introduced here. For those users the honest signal is the label, not the bar.
40
+ ### Added — guards
41
+ - `browser/upload-states.mjs` gains a third spec, measuring what the **engine renders** rather than
42
+ what the source declares: both ways to get this wrong are invisible in a code read. Mutation-checked
43
+ against five defects, including the report's own two — modifier-on-bar, inline `width:100%`, no
44
+ width restored on return, `aria-valuenow` kept, and label ignored.
45
+
6
46
  ## [1.25.0] — 2026-08-13
7
47
  Improvement note #42, found while adopting 1.24.0 — the other half of the same integration.
8
48
  ### Fixed
package/COMPONENTS.md CHANGED
@@ -495,11 +495,17 @@ transfer started; the kit never claims one it is not performing.
495
495
  | *(initial)* | **rest** — chosen, not sent. Size only, no progress bar. |
496
496
  | `.uploading()` | in flight — adds the progress bar |
497
497
  | `.setProgress(pct)` | moves the bar (0–100) |
498
+ | `.waiting(label)` | **sent, waiting on the server** — indeterminate bar, no `aria-valuenow`; `label` is yours (default *Menunggu server…*) |
498
499
  | `.done()` | success — drops the bar, `.fdy-file--success` |
499
500
  | `.fail(msg)` | error — drops the bar, `.fdy-file--error`, `msg` replaces the sub-line |
500
501
  | `.ready()` | back to **rest** (e.g. after a failed attempt the user will retry) |
501
502
  | `.el` | the row element |
502
503
 
504
+ **If your request outlives the transfer, drive `waiting()`.** Server-side work after the last byte —
505
+ OCR, extraction, virus scanning, transcoding — is not uploading, and `setProgress(100)` left standing
506
+ is read as a hang. `waiting()` is the state for it: the label says what the server is doing, and the
507
+ bar stops claiming a percentage it no longer has.
508
+
503
509
  ```js
504
510
  zone.addEventListener('fdy-upload-add', (e) => {
505
511
  if (e.detail.rejected) return; // the kit already rendered the reason
@@ -507,6 +513,8 @@ zone.addEventListener('fdy-upload-add', (e) => {
507
513
  submitBtn.onclick = async () => {
508
514
  row.uploading();
509
515
  await send(file, (pct) => row.setProgress(pct));
516
+ row.waiting('Membaca dokumen…'); // bytes gone, server still working
517
+ await serverFinished();
510
518
  row.done();
511
519
  };
512
520
  });
package/README.id.md CHANGED
@@ -5,7 +5,7 @@
5
5
  > **Lebih banyak _free day_ buat dev — UI kit-nya sudah siap pakai.**
6
6
 
7
7
  [![Live docs](https://img.shields.io/badge/docs-live-2050d8?style=flat-square)](https://cahyo-dimas.github.io/freeday-ui-kit/)
8
- [![Release](https://img.shields.io/badge/release-v1.25.0-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v1.25.0)
8
+ [![Release](https://img.shields.io/badge/release-v1.26.0-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v1.26.0)
9
9
 
10
10
  UI KIT yang token-driven & framework-agnostic — satu sumber kebenaran untuk warna, tipografi,
11
11
  spasi, dan komponen. Blueprint: `docs/superpowers/specs/2026-07-21-freeday-ui-kit-design.md`.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  > **More free days for devs — the UI kit is ready to use.**
6
6
 
7
7
  [![Live docs](https://img.shields.io/badge/docs-live-2050d8?style=flat-square)](https://cahyo-dimas.github.io/freeday-ui-kit/)
8
- [![Release](https://img.shields.io/badge/release-v1.25.0-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v1.25.0)
8
+ [![Release](https://img.shields.io/badge/release-v1.26.0-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v1.26.0)
9
9
 
10
10
  A token-driven, framework-agnostic UI kit — one source of truth for color, typography,
11
11
  spacing, and components. Blueprint: `docs/superpowers/specs/2026-07-21-freeday-ui-kit-design.md`.
@@ -20,8 +20,8 @@
20
20
  * "fdy-upload-add" {file, rejected, reason, row}
21
21
  * "fdy-upload-remove" {file}
22
22
  * `row` is the state machine over the rendered .fdy-file:
23
- * .ready() (rest — where a dropped file starts) / .uploading() / .setProgress(pct) / .done() /
24
- * .fail(msg) / .el
23
+ * .ready() (rest — where a dropped file starts) / .uploading() / .setProgress(pct) /
24
+ * .waiting(label) (sent, awaiting the server) / .done() / .fail(msg) / .el
25
25
  * Note the file list is a SIBLING of the dropzone above, so nothing dispatched on a row would ever
26
26
  * bubble through the zone — which is why removal fires on the zone and not on the row.
27
27
  */
@@ -107,6 +107,13 @@
107
107
  function dropProgress() {
108
108
  if (progressWrap) { progressWrap.remove(); progressWrap = null; bar = null; progressEl = null; }
109
109
  }
110
+ /* Back to a measured bar. While the indeterminate modifier is on it owns the bar's width, so an
111
+ explicit one has to be restored when it comes off: .fdy-progress__bar is a plain block div, and
112
+ with no width at all it fills the track — a full bar, which is the opposite of what 0% means. */
113
+ function determinate() {
114
+ progressEl.classList.remove('fdy-progress--indeterminate');
115
+ if (!bar.style.width) bar.style.width = '0%';
116
+ }
110
117
  return {
111
118
  el: el,
112
119
  /* The state a row starts in: chosen, not yet sent. The size alone — it makes no claim about
@@ -124,13 +131,34 @@
124
131
  icon.innerHTML = FILE_ICON;
125
132
  sub.textContent = fmtSize(file.size) + ' · Mengunggah…';
126
133
  ensureProgress();
134
+ determinate();
127
135
  },
128
136
  setProgress: function (pct) {
129
137
  ensureProgress();
138
+ determinate();
130
139
  var v = Math.max(0, Math.min(100, pct));
131
140
  bar.style.width = v + '%';
132
141
  progressEl.setAttribute('aria-valuenow', String(Math.round(v)));
133
142
  },
143
+ /* The bytes are gone and the server has not answered yet — extraction, scanning, transcoding.
144
+ "Mengunggah…" turns false the moment the last byte leaves, and a determinate bar parked at
145
+ 100% is the most convincing "hung" signal a UI can produce, so this state reports no
146
+ percentage: the bar goes indeterminate and the label belongs to the consumer, because only
147
+ they know what the server is doing ("Membaca PDF…", "Memindai…"). done()/fail()/ready() need
148
+ no counterpart here — they drop the progress element outright, modifier and all. */
149
+ waiting: function (label) {
150
+ el.classList.remove('fdy-file--error', 'fdy-file--success');
151
+ icon.innerHTML = FILE_ICON;
152
+ sub.textContent = fmtSize(file.size) + ' · ' + (label || 'Menunggu server…');
153
+ ensureProgress();
154
+ progressEl.classList.add('fdy-progress--indeterminate');
155
+ /* The modifier styles .fdy-progress__bar, so it must sit on the CONTAINER, and the inline
156
+ width setProgress wrote has to go — an inline style beats any rule the modifier brings.
157
+ aria-valuenow goes with it: a progressbar with no value is precisely what ARIA calls
158
+ indeterminate, which is the contract COMPONENTS.md already states for this component. */
159
+ bar.style.width = '';
160
+ progressEl.removeAttribute('aria-valuenow');
161
+ },
134
162
  done: function () {
135
163
  el.classList.add('fdy-file--success');
136
164
  el.classList.remove('fdy-file--error');
package/dist/freeday.js CHANGED
@@ -3595,8 +3595,8 @@
3595
3595
  * "fdy-upload-add" {file, rejected, reason, row}
3596
3596
  * "fdy-upload-remove" {file}
3597
3597
  * `row` is the state machine over the rendered .fdy-file:
3598
- * .ready() (rest — where a dropped file starts) / .uploading() / .setProgress(pct) / .done() /
3599
- * .fail(msg) / .el
3598
+ * .ready() (rest — where a dropped file starts) / .uploading() / .setProgress(pct) /
3599
+ * .waiting(label) (sent, awaiting the server) / .done() / .fail(msg) / .el
3600
3600
  * Note the file list is a SIBLING of the dropzone above, so nothing dispatched on a row would ever
3601
3601
  * bubble through the zone — which is why removal fires on the zone and not on the row.
3602
3602
  */
@@ -3682,6 +3682,13 @@
3682
3682
  function dropProgress() {
3683
3683
  if (progressWrap) { progressWrap.remove(); progressWrap = null; bar = null; progressEl = null; }
3684
3684
  }
3685
+ /* Back to a measured bar. While the indeterminate modifier is on it owns the bar's width, so an
3686
+ explicit one has to be restored when it comes off: .fdy-progress__bar is a plain block div, and
3687
+ with no width at all it fills the track — a full bar, which is the opposite of what 0% means. */
3688
+ function determinate() {
3689
+ progressEl.classList.remove('fdy-progress--indeterminate');
3690
+ if (!bar.style.width) bar.style.width = '0%';
3691
+ }
3685
3692
  return {
3686
3693
  el: el,
3687
3694
  /* The state a row starts in: chosen, not yet sent. The size alone — it makes no claim about
@@ -3699,13 +3706,34 @@
3699
3706
  icon.innerHTML = FILE_ICON;
3700
3707
  sub.textContent = fmtSize(file.size) + ' · Mengunggah…';
3701
3708
  ensureProgress();
3709
+ determinate();
3702
3710
  },
3703
3711
  setProgress: function (pct) {
3704
3712
  ensureProgress();
3713
+ determinate();
3705
3714
  var v = Math.max(0, Math.min(100, pct));
3706
3715
  bar.style.width = v + '%';
3707
3716
  progressEl.setAttribute('aria-valuenow', String(Math.round(v)));
3708
3717
  },
3718
+ /* The bytes are gone and the server has not answered yet — extraction, scanning, transcoding.
3719
+ "Mengunggah…" turns false the moment the last byte leaves, and a determinate bar parked at
3720
+ 100% is the most convincing "hung" signal a UI can produce, so this state reports no
3721
+ percentage: the bar goes indeterminate and the label belongs to the consumer, because only
3722
+ they know what the server is doing ("Membaca PDF…", "Memindai…"). done()/fail()/ready() need
3723
+ no counterpart here — they drop the progress element outright, modifier and all. */
3724
+ waiting: function (label) {
3725
+ el.classList.remove('fdy-file--error', 'fdy-file--success');
3726
+ icon.innerHTML = FILE_ICON;
3727
+ sub.textContent = fmtSize(file.size) + ' · ' + (label || 'Menunggu server…');
3728
+ ensureProgress();
3729
+ progressEl.classList.add('fdy-progress--indeterminate');
3730
+ /* The modifier styles .fdy-progress__bar, so it must sit on the CONTAINER, and the inline
3731
+ width setProgress wrote has to go — an inline style beats any rule the modifier brings.
3732
+ aria-valuenow goes with it: a progressbar with no value is precisely what ARIA calls
3733
+ indeterminate, which is the contract COMPONENTS.md already states for this component. */
3734
+ bar.style.width = '';
3735
+ progressEl.removeAttribute('aria-valuenow');
3736
+ },
3709
3737
  done: function () {
3710
3738
  el.classList.add('fdy-file--success');
3711
3739
  el.classList.remove('fdy-file--error');
@@ -184,7 +184,7 @@ live docs also have a copy button per component.
184
184
  ```bash
185
185
  npm i @cahyo-dimas/freeday
186
186
  ```
187
- Lands in `package.json` as `"@cahyo-dimas/freeday": "^1.25.0"` (public npm package). `dist/` is
187
+ Lands in `package.json` as `"@cahyo-dimas/freeday": "^1.26.0"` (public npm package). `dist/` is
188
188
  committed and published → no build step; `npm ci` runs without auth.
189
189
 
190
190
  ### 2. Import the CSS + enhancers **once** in your entry (`src/main.ts`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cahyo-dimas/freeday",
3
- "version": "1.25.0",
3
+ "version": "1.26.0",
4
4
  "description": "Freeday — token-driven, framework-agnostic UI KIT (design source-of-truth).",
5
5
  "type": "module",
6
6
  "license": "MIT",