@cahyo-dimas/freeday 2.1.0 → 2.2.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.
@@ -29,6 +29,28 @@
29
29
 
30
30
  var NS = 'http://www.w3.org/2000/svg';
31
31
 
32
+ /* User-facing strings. Two of them, and both shipped wrong until 2.2.0: the legend's fallback
33
+ * label read `Seri 1` — Indonesian, three months after 2.0.0 turned every enhancer English —
34
+ * and the donut's centre caption was hard-coded, so no host could rename it. Neither was
35
+ * reachable by the guards: one goes into the DOM through `createTextNode`, the other through
36
+ * `innerHTML`, and both guards look for `textContent` / `setAttribute`. Overridable per element
37
+ * with `data-fdy-text-<key>`, like every other enhancer. */
38
+ var TEXT = {
39
+ series: 'Series {n}',
40
+ total: 'Total'
41
+ };
42
+ function textAttr(root, key) {
43
+ if (!root || !root.getAttribute) return null;
44
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
45
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
46
+ }
47
+ function textOf(root, key, vars) {
48
+ var custom = textAttr(root, key);
49
+ var s = custom != null && custom !== '' ? custom : TEXT[key];
50
+ if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
51
+ return s;
52
+ }
53
+
32
54
  // Categorical chart palette: 8 validated fixed-order slots (--chart-1..8). Series index i
33
55
  // (0-based) -> slot i+1; series beyond the 8-slot cap reuse --chart-8 (never cycled).
34
56
  function chartSlotVar(i) { return 'var(--chart-' + (i < 8 ? i + 1 : 8) + ')'; }
@@ -237,7 +259,7 @@
237
259
  var li = document.createElement('li');
238
260
  var sw = document.createElement('span'); sw.className = 'fdy-chart__swatch'; sw.style.background = colorFor(si);
239
261
  li.appendChild(sw);
240
- li.appendChild(document.createTextNode(s.label || ('Seri ' + (si + 1))));
262
+ li.appendChild(document.createTextNode(s.label || textOf(el, 'series', { n: si + 1 })));
241
263
  legend.appendChild(li);
242
264
  });
243
265
  el.appendChild(legend);
@@ -411,8 +433,16 @@
411
433
  ring.style.background = 'conic-gradient(' + stops.join(',') + ')';
412
434
  var center = document.createElement('div'); center.className = 'fdy-donut__center';
413
435
  var centerLabel = el.getAttribute('data-fdy-center');
414
- center.innerHTML = centerLabel ? '<b></b>' : '<b></b><span>Total</span>';
415
- center.querySelector('b').textContent = centerLabel != null ? centerLabel : String(total);
436
+ var centerValue = document.createElement('b');
437
+ centerValue.textContent = centerLabel != null ? centerLabel : String(total);
438
+ center.appendChild(centerValue);
439
+ /* Built rather than assigned as innerHTML: the caption is overridable now, and an author's
440
+ string is not markup. */
441
+ if (!centerLabel) {
442
+ var centerCaption = document.createElement('span');
443
+ centerCaption.textContent = textOf(el, 'total');
444
+ center.appendChild(centerCaption);
445
+ }
416
446
  ring.appendChild(center);
417
447
  var svg = svgEl('svg');
418
448
  svg.setAttribute('class', 'fdy-donut__hit');
@@ -3,12 +3,12 @@
3
3
  * Locale comes from <html lang> (via Intl), month/weekday/value formatting is not hardcoded.
4
4
  *
5
5
  * Markup contract:
6
- * - Single: <div data-fdy-datepicker data-value="2026-07-21" data-label="Tanggal unggah"
7
- * data-placeholder="Pilih tanggal" data-min="2026-01-01" data-max="2026-12-31"></div>
8
- * - Range: <div data-fdy-daterange role="group" aria-label="Rentang tanggal">
9
- * <div data-fdy-datepicker data-role="from" data-placeholder="Dari"></div>
6
+ * - Single: <div data-fdy-datepicker data-value="2026-07-21" data-label="Upload date"
7
+ * data-placeholder="Choose a date" data-min="2026-01-01" data-max="2026-12-31"></div>
8
+ * - Range: <div data-fdy-daterange role="group" aria-label="Date range">
9
+ * <div data-fdy-datepicker data-role="from" data-placeholder="From"></div>
10
10
  * <span class="fdy-daterange__sep">–</span>
11
- * <div data-fdy-datepicker data-role="to" data-placeholder="Sampai"></div>
11
+ * <div data-fdy-datepicker data-role="to" data-placeholder="To"></div>
12
12
  * </div>
13
13
  * The range links the two: the end can never precede the start (out-of-range days disable).
14
14
  *
@@ -24,6 +24,47 @@
24
24
  weekday names back automatically. The FALLBACK follows the kit's default language, or a
25
25
  page without `lang` would read English labels around Indonesian month names. */
26
26
  var LOCALE = document.documentElement.getAttribute('lang') || 'en';
27
+
28
+ /* User-facing strings. English by default, and every one overridable per element with
29
+ * `data-fdy-text-<key>`, so a host that speaks another language (an Indonesian app on the raw
30
+ * path, and every Blazor app, whose picker IS this enhancer) supplies its own without forking
31
+ * this file.
32
+ *
33
+ * This table arrived late, in 2.2.0: the ten labels below were written as literals passed to
34
+ * `navButton()` / `titleButton()`, so the guard that proves no enhancer string is hard-coded
35
+ * never saw them — it looks for the line that writes to the DOM, and here that line only ever
36
+ * sees a variable. Month and weekday names are NOT here on purpose: they come from `Intl`
37
+ * through the page's `lang`, which is a better hatch than anything the kit could invent.
38
+ * The `{label}` in the three title strings is the period the button drills into. */
39
+ var TEXT = {
40
+ label: 'Date',
41
+ placeholder: 'Choose a date',
42
+ prevMonth: 'Previous month',
43
+ nextMonth: 'Next month',
44
+ prevYear: 'Previous year',
45
+ nextYear: 'Next year',
46
+ prevYears: 'Previous years',
47
+ nextYears: 'Next years',
48
+ chooseMonth: '{label}, choose month',
49
+ chooseYear: '{label}, choose year',
50
+ backToMonths: '{start} to {end}, back to months'
51
+ };
52
+ /* HTML lowercases attribute names, so a camelCase key like `prevMonth` can only ever be written
53
+ as `data-fdy-text-prevmonth`, while the kebab form anybody would reach for,
54
+ `data-fdy-text-prev-month`, becomes a DIFFERENT attribute the enhancer never reads, and the
55
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
56
+ still resolves. */
57
+ function textAttr(root, key) {
58
+ if (!root || !root.getAttribute) return null;
59
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
60
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
61
+ }
62
+ function textOf(root, key, vars) {
63
+ var custom = textAttr(root, key);
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
+ }
27
68
  var uidSeq = 0;
28
69
  function uid(p) { uidSeq += 1; return p + '-' + uidSeq; }
29
70
  function pad(n) { return n < 10 ? '0' + n : '' + n; }
@@ -61,8 +102,8 @@
61
102
  wrap.dataset.fdyDpReady = '1';
62
103
  wrap.classList.add('fdy-datepicker');
63
104
 
64
- var placeholder = wrap.getAttribute('data-placeholder') || 'Choose a date';
65
- var label = wrap.getAttribute('data-label') || 'Date';
105
+ var placeholder = wrap.getAttribute('data-placeholder') || textOf(wrap, 'placeholder');
106
+ var label = wrap.getAttribute('data-label') || textOf(wrap, 'label');
66
107
  var selected = parseISO(wrap.getAttribute('data-value'));
67
108
  var minDate = parseISO(wrap.getAttribute('data-min'));
68
109
  var maxDate = parseISO(wrap.getAttribute('data-max'));
@@ -160,16 +201,16 @@
160
201
  panel.innerHTML = '';
161
202
  var head = document.createElement('div');
162
203
  head.className = 'fdy-cal__head';
163
- var title = titleButton(monthFmt.format(view), monthFmt.format(view) + ', choose month', function () {
204
+ var title = titleButton(monthFmt.format(view), textOf(wrap, 'chooseMonth', { label: monthFmt.format(view) }), function () {
164
205
  mode = 'months';
165
206
  focusMonth = view.getMonth();
166
207
  render();
167
208
  focusMonthCell();
168
209
  });
169
210
  panel.setAttribute('aria-labelledby', title.id);
170
- head.appendChild(navButton('‹', 'Previous month', function () { view = addMonths(view, -1); render(); }));
211
+ head.appendChild(navButton('‹', textOf(wrap, 'prevMonth'), function () { view = addMonths(view, -1); render(); }));
171
212
  head.appendChild(title);
172
- head.appendChild(navButton('›', 'Next month', function () { view = addMonths(view, 1); render(); }));
213
+ head.appendChild(navButton('›', textOf(wrap, 'nextMonth'), function () { view = addMonths(view, 1); render(); }));
173
214
  panel.appendChild(head);
174
215
 
175
216
  var grid = document.createElement('div');
@@ -226,16 +267,16 @@
226
267
  var year = view.getFullYear();
227
268
  var head = document.createElement('div');
228
269
  head.className = 'fdy-cal__head';
229
- var title = titleButton(String(year), year + ', choose year', function () {
270
+ var title = titleButton(String(year), textOf(wrap, 'chooseYear', { label: year }), function () {
230
271
  mode = 'years';
231
272
  focusYear = year;
232
273
  render();
233
274
  focusYearCell();
234
275
  });
235
276
  panel.setAttribute('aria-labelledby', title.id);
236
- head.appendChild(navButton('‹', 'Previous year', function () { view = addMonths(view, -12); render(); focusMonthCell(); }));
277
+ head.appendChild(navButton('‹', textOf(wrap, 'prevYear'), function () { view = addMonths(view, -12); render(); focusMonthCell(); }));
237
278
  head.appendChild(title);
238
- head.appendChild(navButton('›', 'Next year', function () { view = addMonths(view, 12); render(); focusMonthCell(); }));
279
+ head.appendChild(navButton('›', textOf(wrap, 'nextYear'), function () { view = addMonths(view, 12); render(); focusMonthCell(); }));
239
280
  panel.appendChild(head);
240
281
 
241
282
  var grid = document.createElement('div');
@@ -287,16 +328,16 @@
287
328
  var end = start + YEARS_PER_PAGE - 1;
288
329
  var head = document.createElement('div');
289
330
  head.className = 'fdy-cal__head';
290
- var title = titleButton(start + ' – ' + end, start + ' to ' + end + ', back to months', function () {
331
+ var title = titleButton(start + ' – ' + end, textOf(wrap, 'backToMonths', { start: start, end: end }), function () {
291
332
  mode = 'months';
292
333
  focusMonth = view.getMonth();
293
334
  render();
294
335
  focusMonthCell();
295
336
  });
296
337
  panel.setAttribute('aria-labelledby', title.id);
297
- head.appendChild(navButton('‹', 'Previous years', function () { moveYearFocus(focusYear - YEARS_PER_PAGE); }));
338
+ head.appendChild(navButton('‹', textOf(wrap, 'prevYears'), function () { moveYearFocus(focusYear - YEARS_PER_PAGE); }));
298
339
  head.appendChild(title);
299
- head.appendChild(navButton('›', 'Next years', function () { moveYearFocus(focusYear + YEARS_PER_PAGE); }));
340
+ head.appendChild(navButton('›', textOf(wrap, 'nextYears'), function () { moveYearFocus(focusYear + YEARS_PER_PAGE); }));
300
341
  panel.appendChild(head);
301
342
 
302
343
  var grid = document.createElement('div');
@@ -457,7 +498,46 @@
457
498
 
458
499
  var _pop = null;
459
500
  function popCtl() { if (_pop === null && window.FreedayPopover) _pop = window.FreedayPopover.attach(panel, trigger); return _pop; }
501
+
502
+ /* The three field states the CSS has always styled (`:disabled`, `[aria-readonly="true"]`,
503
+ `[aria-invalid="true"]`) and this enhancer never set, so only the stacks that re-implement
504
+ the control natively had them. Read from the seed at init and settable afterwards, because
505
+ a host that renders once — every Blazor wrapper does, `ShouldRender => false` — cannot
506
+ express a later change any other way. */
507
+ function flagOf(name) {
508
+ var v = wrap.getAttribute('data-' + name);
509
+ return v != null && v !== 'false';
510
+ }
511
+ /* Named `state*`, not `is*`: this file already has an `isDisabled(date)` deciding whether a
512
+ DAY falls outside min/max, and shadowing it with a boolean made the day grid throw on every
513
+ render — silently, since the panel still opened and only its cells went missing. */
514
+ var stateDisabled = flagOf('disabled');
515
+ var stateReadonly = flagOf('readonly');
516
+ var stateInvalid = flagOf('invalid');
517
+ /* `data-id` and `data-describedby`: the trigger this enhancer BUILDS is the element a form
518
+ has to point its label and its error text at, and the raw path had no way to say so. */
519
+ if (wrap.getAttribute('data-id')) trigger.id = wrap.getAttribute('data-id');
520
+ if (wrap.getAttribute('data-describedby')) trigger.setAttribute('aria-describedby', wrap.getAttribute('data-describedby'));
521
+
522
+ function applyState() {
523
+ trigger.disabled = stateDisabled;
524
+ if (stateReadonly) trigger.setAttribute('aria-readonly', 'true');
525
+ else trigger.removeAttribute('aria-readonly');
526
+ if (stateInvalid) trigger.setAttribute('aria-invalid', 'true');
527
+ else trigger.removeAttribute('aria-invalid');
528
+ wrap.classList.toggle('fdy-datepicker--error', stateInvalid);
529
+ if ((stateDisabled || stateReadonly) && !panel.hidden) close(false);
530
+ }
531
+ function setState(next) {
532
+ if (!next) return;
533
+ if (next.disabled != null) stateDisabled = !!next.disabled;
534
+ if (next.readonly != null) stateReadonly = !!next.readonly;
535
+ if (next.invalid != null) stateInvalid = !!next.invalid;
536
+ applyState();
537
+ }
538
+
460
539
  function open() {
540
+ if (stateDisabled || stateReadonly) return;
461
541
  if (!panel.hidden) return;
462
542
  mode = 'days';
463
543
  focusDate = selected || focusDate || new Date();
@@ -487,8 +567,11 @@
487
567
 
488
568
  updateDisplay();
489
569
 
570
+ applyState();
571
+
490
572
  var api = {
491
573
  wrap: wrap,
574
+ setState: setState,
492
575
  getValue: function () { return selected ? toISO(selected) : ''; },
493
576
  clear: function () { selected = null; updateDisplay(); if (!panel.hidden) render(); },
494
577
  setMin: function (iso) { minDate = parseISO(iso); if (!panel.hidden) render(); },
@@ -548,5 +631,14 @@
548
631
  initAll();
549
632
  }
550
633
 
551
- window.FreedayDatepicker = { init: initPicker, initAll: initAll };
634
+ window.FreedayDatepicker = {
635
+ init: initPicker,
636
+ initAll: initAll,
637
+ /* A host that rendered its seed once and cannot re-render it (Blazor) still has to be able to
638
+ disable, lock or invalidate the field later. */
639
+ setState: function (root, state) {
640
+ var api = root && root._fdyDp ? root._fdyDp : null;
641
+ if (api && api.setState) api.setState(state);
642
+ }
643
+ };
552
644
  })();
@@ -210,5 +210,22 @@
210
210
  if (root && root._fdyCombo) root._fdyCombo.setValue(value);
211
211
  }
212
212
 
213
- window.FreedayCombo = { init: initCombo, initAll: initAll, setValue: setValue };
213
+ /* Beside setValue for the same reason it exists: a host that renders its markup once (every
214
+ Blazor wrapper, `ShouldRender => false`) cannot express a later state change any other way,
215
+ and a parameter that silently stops working after the first render is worse than none. */
216
+ function setState(root, state) {
217
+ var button = root ? root.querySelector('.fdy-combo__button') : null;
218
+ if (!button || !state) return;
219
+ if (state.disabled != null) button.disabled = !!state.disabled;
220
+ if (state.readonly != null) {
221
+ if (state.readonly) button.setAttribute('aria-readonly', 'true');
222
+ else button.removeAttribute('aria-readonly');
223
+ }
224
+ if (state.invalid != null) {
225
+ if (state.invalid) button.setAttribute('aria-invalid', 'true');
226
+ else button.removeAttribute('aria-invalid');
227
+ }
228
+ }
229
+
230
+ window.FreedayCombo = { init: initCombo, initAll: initAll, setValue: setValue, setState: setState };
214
231
  })();
@@ -47,6 +47,8 @@
47
47
  filterText: 'Contains text',
48
48
  filterTextPlaceholder: 'Contains…',
49
49
  filterEnum: 'Show values',
50
+ filterMin: 'Min',
51
+ filterMax: 'Max',
50
52
  filterRange: 'Value range',
51
53
  reset: 'Reset',
52
54
  close: 'Close',
@@ -319,8 +321,8 @@
319
321
  pop.appendChild(filterTitle(textOf(root, 'filterRange')));
320
322
  var range = document.createElement('div');
321
323
  range.className = 'fdy-filter__range';
322
- var minI = numberInput('Min', f.min);
323
- var maxI = numberInput('Maks', f.max);
324
+ var minI = numberInput(textOf(root, 'filterMin'), f.min);
325
+ var maxI = numberInput(textOf(root, 'filterMax'), f.max);
324
326
  var applyRange = function () {
325
327
  f.min = minI.value !== '' ? parseNum(minI.value) : null;
326
328
  f.max = maxI.value !== '' ? parseNum(maxI.value) : null;
@@ -14,6 +14,24 @@
14
14
  (function () {
15
15
  'use strict';
16
16
 
17
+ /* User-facing strings, overridable per element with `data-fdy-text-<key>`. One entry, and it
18
+ * still needed the table: it reached the DOM as an argument to a helper, which is how it sat
19
+ * outside every string guard until 2.2.0. */
20
+ var TEXT = {
21
+ label: 'Choose a time'
22
+ };
23
+ function textAttr(root, key) {
24
+ if (!root || !root.getAttribute) return null;
25
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
26
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
27
+ }
28
+ function textOf(root, key, vars) {
29
+ var custom = textAttr(root, key);
30
+ var s = custom != null && custom !== '' ? custom : TEXT[key];
31
+ if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
32
+ return s;
33
+ }
34
+
17
35
  var seq = 0;
18
36
  var CLOCK = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"></circle><path d="M12 7v5l3 2"></path></svg>';
19
37
 
@@ -28,7 +46,7 @@
28
46
  wrap.dataset.fdyTpReady = '1';
29
47
  wrap.classList.add('fdy-timepicker');
30
48
 
31
- var label = wrap.getAttribute('data-label') || 'Choose a time';
49
+ var label = wrap.getAttribute('data-label') || textOf(wrap, 'label');
32
50
  var placeholder = wrap.getAttribute('data-placeholder') || '--:--';
33
51
  var step = Math.max(1, parseInt(wrap.getAttribute('data-step') || '30', 10));
34
52
  var minM = valid(wrap.getAttribute('data-min')) ? toMin(wrap.getAttribute('data-min')) : 0;