@cahyo-dimas/freeday 1.41.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 +131 -0
- package/COMPONENTS.md +15 -1
- package/adapters/blazor/FdyCfl.razor +24 -1
- package/adapters/blazor/FdyCfl.razor.cs +65 -0
- package/adapters/react/components/FdyCfl.tsx +82 -10
- package/adapters/vue/components/FdyCfl.vue +73 -11
- package/dist/freeday-carousel.js +11 -1
- package/dist/freeday-cascade.js +11 -1
- package/dist/freeday-cfl.js +11 -1
- package/dist/freeday-form.js +6 -1
- package/dist/freeday-mask.js +11 -1
- package/dist/freeday-stepper.js +11 -1
- package/dist/freeday-table.js +11 -1
- package/dist/freeday-toast.js +11 -1
- package/dist/freeday-upload.js +11 -1
- package/dist/freeday.bundle.css +44 -1
- package/dist/freeday.css +44 -1
- package/dist/freeday.js +94 -9
- package/package.json +2 -2
- package/src/components/badge.css +19 -0
- package/src/components/composition.css +12 -0
- package/src/components/file-upload.css +6 -1
- package/src/components/table.css +7 -0
|
@@ -23,7 +23,10 @@ interface CflPage {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const props = defineProps<{
|
|
26
|
-
|
|
26
|
+
/** Single: `Row | null`. With `multiple`, an array — `Row[] | null`, where null and [] both mean
|
|
27
|
+
* nothing picked. The enhancer has had `data-fdy-cfl-multiple` all along; this is the typed
|
|
28
|
+
* wrappers catching up (#019). */
|
|
29
|
+
modelValue: Row | Row[] | null;
|
|
27
30
|
fetchPage: (query: string, page: number) => Promise<CflPage>;
|
|
28
31
|
columns: ReadonlyArray<CflColumn>;
|
|
29
32
|
display: (row: Row) => string;
|
|
@@ -46,6 +49,16 @@ const props = defineProps<{
|
|
|
46
49
|
closeLabel?: string;
|
|
47
50
|
/** aria-label for the button that opens the picker. Default 'Open search'. */
|
|
48
51
|
openLabel?: string;
|
|
52
|
+
/** Tick rows and commit them together, instead of committing the row that was clicked. The kit's
|
|
53
|
+
* own enhancer offers this (`data-fdy-cfl-multiple`); a screen that gathers six expense claims
|
|
54
|
+
* onto one document wants one dialog, not six. */
|
|
55
|
+
multiple?: boolean;
|
|
56
|
+
/** Footer label while picking, `{n}` replaced by the tick count. Default '{n} selected'. */
|
|
57
|
+
selectedText?: string;
|
|
58
|
+
/** The multi-select commit button. Default 'Confirm'. */
|
|
59
|
+
confirmText?: string;
|
|
60
|
+
/** Footer hint in single mode. Default 'Click a row to choose it'. */
|
|
61
|
+
hintText?: string;
|
|
49
62
|
placeholder?: string;
|
|
50
63
|
disabled?: boolean;
|
|
51
64
|
/** Locked/view mode: shows the picked value (focusable, copyable), but the search dialog can't be opened. Unlike `disabled`, it keeps tab order and isn't greyed. */
|
|
@@ -62,8 +75,8 @@ const props = defineProps<{
|
|
|
62
75
|
}>();
|
|
63
76
|
|
|
64
77
|
const emit = defineEmits<{
|
|
65
|
-
'update:modelValue': [value: Row | null];
|
|
66
|
-
change: [value: Row | null];
|
|
78
|
+
'update:modelValue': [value: Row | Row[] | null];
|
|
79
|
+
change: [value: Row | Row[] | null];
|
|
67
80
|
}>();
|
|
68
81
|
|
|
69
82
|
const baseId: string = useId();
|
|
@@ -91,13 +104,20 @@ const isReadonly: ComputedRef<boolean> = computed((): boolean => props.readonly
|
|
|
91
104
|
|
|
92
105
|
const showClear: ComputedRef<boolean> = computed(
|
|
93
106
|
(): boolean =>
|
|
94
|
-
props.clearable === true &&
|
|
107
|
+
props.clearable === true && currentRows.value.length > 0 && isDisabled.value === false && isReadonly.value === false,
|
|
95
108
|
);
|
|
96
109
|
const clearLabelText: ComputedRef<string> = computed((): string => props.clearLabel ?? 'Clear selection');
|
|
97
110
|
const isInvalid: ComputedRef<boolean> = computed((): boolean => props.invalid === true);
|
|
98
|
-
|
|
99
|
-
|
|
111
|
+
/* `display()` takes one row, so in multi the field states HOW MANY — naming one of six would be a
|
|
112
|
+
lie, and naming all six does not fit a control that is 22rem wide. */
|
|
113
|
+
const currentRows: ComputedRef<Row[]> = computed((): Row[] =>
|
|
114
|
+
Array.isArray(props.modelValue) ? props.modelValue : props.modelValue !== null ? [props.modelValue as Row] : [],
|
|
100
115
|
);
|
|
116
|
+
const selectedTextFor = (n: number): string => (props.selectedText ?? '{n} selected').replace('{n}', String(n));
|
|
117
|
+
const displayValue: ComputedRef<string> = computed((): string => {
|
|
118
|
+
if (props.multiple === true) return currentRows.value.length === 0 ? '' : selectedTextFor(currentRows.value.length);
|
|
119
|
+
return props.modelValue !== null ? props.display(props.modelValue as Row) : '';
|
|
120
|
+
});
|
|
101
121
|
// The results <table> (owner of `resultsId`) only renders in the rows branch, so gate the
|
|
102
122
|
// search input's aria refs on rows existing — otherwise they'd dangle during loading/empty/error.
|
|
103
123
|
const hasRows: ComputedRef<boolean> = computed((): boolean => rows.value.length > 0);
|
|
@@ -181,6 +201,37 @@ function setActive(index: number): void {
|
|
|
181
201
|
});
|
|
182
202
|
}
|
|
183
203
|
|
|
204
|
+
/* The ticks live here, not in `modelValue`, because a multi dialog is only committed at Confirm:
|
|
205
|
+
closing it must leave the caller's value exactly as it was. Seeded from `modelValue` on open. */
|
|
206
|
+
const picked: Ref<Row[]> = ref([]) as Ref<Row[]>;
|
|
207
|
+
|
|
208
|
+
const pickedKeys: ComputedRef<Set<string>> = computed(
|
|
209
|
+
(): Set<string> => new Set(picked.value.map((r: Row): string => props.rowKey(r)))
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
function isPicked(row: Row): boolean {
|
|
213
|
+
return pickedKeys.value.has(props.rowKey(row));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function togglePick(row: Row): void {
|
|
217
|
+
const key: string = props.rowKey(row);
|
|
218
|
+
const at: number = picked.value.findIndex((r: Row): boolean => props.rowKey(r) === key);
|
|
219
|
+
if (at === -1) picked.value = [...picked.value, row];
|
|
220
|
+
else picked.value = picked.value.filter((_: Row, i: number): boolean => i !== at);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/* A click means "tick this" in multi and "this is my answer" in single — the whole difference. */
|
|
224
|
+
function onRowClick(row: Row): void {
|
|
225
|
+
if (props.multiple === true) togglePick(row);
|
|
226
|
+
else commit(row);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function confirmPicks(): void {
|
|
230
|
+
emit('update:modelValue', picked.value);
|
|
231
|
+
emit('change', picked.value);
|
|
232
|
+
closeDialog();
|
|
233
|
+
}
|
|
234
|
+
|
|
184
235
|
function commit(row: Row | null): void {
|
|
185
236
|
emit('update:modelValue', row);
|
|
186
237
|
emit('change', row);
|
|
@@ -190,6 +241,7 @@ function commit(row: Row | null): void {
|
|
|
190
241
|
/* Unsetting is not "picking nothing" — it must not open or close the dialog, and it must leave focus
|
|
191
242
|
on a control that still exists, so focus returns to the trigger beside it. */
|
|
192
243
|
function clearValue(): void {
|
|
244
|
+
picked.value = [];
|
|
193
245
|
emit('update:modelValue', null);
|
|
194
246
|
emit('change', null);
|
|
195
247
|
triggerEl.value?.focus();
|
|
@@ -221,7 +273,7 @@ function onKeydown(e: KeyboardEvent): void {
|
|
|
221
273
|
const row: Row | undefined = activeIndex.value >= 0 ? rows.value[activeIndex.value] : undefined;
|
|
222
274
|
if (row !== undefined) {
|
|
223
275
|
e.preventDefault();
|
|
224
|
-
|
|
276
|
+
onRowClick(row);
|
|
225
277
|
}
|
|
226
278
|
break;
|
|
227
279
|
}
|
|
@@ -244,6 +296,8 @@ function openDialog(): void {
|
|
|
244
296
|
hasMore.value = false;
|
|
245
297
|
error.value = null;
|
|
246
298
|
activeIndex.value = -1;
|
|
299
|
+
/* Re-seeded per open, so Cancel really is a cancel: the ticks start as whatever the caller holds. */
|
|
300
|
+
picked.value = props.multiple === true ? [...currentRows.value] : [];
|
|
247
301
|
dialogEl.value?.showModal();
|
|
248
302
|
void nextTick((): void => searchEl.value?.focus());
|
|
249
303
|
void loadPage(0, false);
|
|
@@ -361,6 +415,7 @@ onBeforeUnmount((): void => {
|
|
|
361
415
|
<table :id="resultsId" class="fdy-table" aria-label="Search results">
|
|
362
416
|
<thead>
|
|
363
417
|
<tr>
|
|
418
|
+
<th v-if="multiple === true" scope="col"><span class="fdy-visually-hidden">{{ selectedTextFor(picked.length) }}</span></th>
|
|
364
419
|
<th v-for="col in columns" :key="col.key" scope="col">{{ col.label }}</th>
|
|
365
420
|
</tr>
|
|
366
421
|
</thead>
|
|
@@ -370,10 +425,14 @@ onBeforeUnmount((): void => {
|
|
|
370
425
|
:id="rowId(i)"
|
|
371
426
|
:key="rowKey(row)"
|
|
372
427
|
class="fdy-cfl__row"
|
|
373
|
-
:
|
|
374
|
-
|
|
428
|
+
:class="{ 'is-active': i === activeIndex }"
|
|
429
|
+
:aria-selected="multiple === true ? (isPicked(row) ? 'true' : 'false') : i === activeIndex ? 'true' : undefined"
|
|
430
|
+
@click="onRowClick(row)"
|
|
375
431
|
@mousemove="setActive(i)"
|
|
376
432
|
>
|
|
433
|
+
<td v-if="multiple === true" class="fdy-cfl__check">
|
|
434
|
+
<input class="fdy-checkbox" type="checkbox" tabindex="-1" :checked="isPicked(row)" aria-hidden="true" />
|
|
435
|
+
</td>
|
|
377
436
|
<td v-for="col in columns" :key="col.key">{{ cellText(row, col.key) }}</td>
|
|
378
437
|
</tr>
|
|
379
438
|
</tbody>
|
|
@@ -381,7 +440,7 @@ onBeforeUnmount((): void => {
|
|
|
381
440
|
|
|
382
441
|
<div v-if="error !== null" class="fdy-cfl__empty" role="alert" style="padding:var(--space-4) var(--space-5)">
|
|
383
442
|
<p style="margin:0 0 var(--space-3)">{{ error.message }}</p>
|
|
384
|
-
<button class="fdy-btn fdy-btn--sm" type="button" @click="retry">
|
|
443
|
+
<button class="fdy-btn fdy-btn--sm" type="button" @click="retry">{{ retryText ?? 'Try again' }}</button>
|
|
385
444
|
</div>
|
|
386
445
|
<div v-else-if="hasMore" style="padding:var(--space-3) var(--space-4);text-align:center">
|
|
387
446
|
<button class="fdy-btn fdy-btn--ghost fdy-btn--sm" type="button" :disabled="loading" @click="loadMore">
|
|
@@ -393,9 +452,12 @@ onBeforeUnmount((): void => {
|
|
|
393
452
|
</div>
|
|
394
453
|
|
|
395
454
|
<div class="fdy-modal__footer">
|
|
396
|
-
<span class="fdy-cfl__count"
|
|
455
|
+
<span class="fdy-cfl__count" aria-live="polite">
|
|
456
|
+
{{ multiple === true ? selectedTextFor(picked.length) : (hintText ?? 'Click a row to choose it') }}
|
|
457
|
+
</span>
|
|
397
458
|
<div class="fdy-cfl__actions">
|
|
398
459
|
<button class="fdy-btn fdy-btn--ghost" type="button" @click="closeDialog">{{ closeLabel ?? 'Close' }}</button>
|
|
460
|
+
<button v-if="multiple === true" class="fdy-btn" type="button" @click="confirmPicks">{{ confirmText ?? 'Confirm' }}</button>
|
|
399
461
|
</div>
|
|
400
462
|
</div>
|
|
401
463
|
</dialog>
|
package/dist/freeday-carousel.js
CHANGED
|
@@ -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
|
|
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;
|
package/dist/freeday-cascade.js
CHANGED
|
@@ -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
|
|
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;
|
package/dist/freeday-cfl.js
CHANGED
|
@@ -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
|
|
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;
|
package/dist/freeday-form.js
CHANGED
|
@@ -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
|
-
|
|
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'];
|
package/dist/freeday-mask.js
CHANGED
|
@@ -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
|
|
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;
|
package/dist/freeday-stepper.js
CHANGED
|
@@ -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
|
|
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;
|
package/dist/freeday-table.js
CHANGED
|
@@ -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
|
|
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;
|
package/dist/freeday-toast.js
CHANGED
|
@@ -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
|
|
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;
|
package/dist/freeday-upload.js
CHANGED
|
@@ -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
|
|
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;
|
package/dist/freeday.bundle.css
CHANGED
|
@@ -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. */
|
|
@@ -1089,6 +1108,18 @@ a { color: var(--color-primary); }
|
|
|
1089
1108
|
.fdy-stat__value{font-family:var(--font-display);font-size:var(--text-3xl);font-weight:var(--weight-bold);letter-spacing:var(--tracking-tighter);line-height:1;color:var(--color-text);font-variant-numeric:tabular-nums;}
|
|
1090
1109
|
.fdy-stat__value small{font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text-muted);}
|
|
1091
1110
|
.fdy-stat__meta{font-size:var(--text-sm);color:var(--color-text-muted);display:flex;align-items:center;gap:var(--space-2);}
|
|
1111
|
+
/* A stat value is display type sized for a COUNT — "1,284" — and a back office puts money in it.
|
|
1112
|
+
"IDR 300,000.00" needs 224px at --text-3xl and the grid's own track is 11rem, so it wrapped
|
|
1113
|
+
between the currency and the number: two lines of 31px where the design says one. Measured, not
|
|
1114
|
+
guessed (#020).
|
|
1115
|
+
The value shrinks only when its own column is too narrow to hold it, so a wide dashboard keeps
|
|
1116
|
+
exactly the type it has today. Scoped to a stat INSIDE .fdy-stats, where the width comes from the
|
|
1117
|
+
grid track — `container-type:inline-size` on a standalone .fdy-stat would stop it sizing to its
|
|
1118
|
+
own content. The unconditional rule above stays as the fallback wherever @container is missing. */
|
|
1119
|
+
@supports (container-type:inline-size){
|
|
1120
|
+
.fdy-stats>.fdy-stat{container-type:inline-size;}
|
|
1121
|
+
.fdy-stats>.fdy-stat .fdy-stat__value{font-size:clamp(var(--text-xl),11cqw,var(--text-3xl));}
|
|
1122
|
+
}
|
|
1092
1123
|
.fdy-stats--boxed{background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);gap:0;}
|
|
1093
1124
|
.fdy-stats--boxed .fdy-stat{padding:var(--space-5);border-right:var(--bw) solid var(--color-border-muted);}
|
|
1094
1125
|
.fdy-stats--boxed .fdy-stat:last-child{border-right:0;}
|
|
@@ -1256,7 +1287,12 @@ a { color: var(--color-primary); }
|
|
|
1256
1287
|
.fdy-dropzone__text{display:flex;flex-direction:column;gap:var(--space-1);}
|
|
1257
1288
|
|
|
1258
1289
|
/* File rows (list of added/uploaded/rejected files) */
|
|
1259
|
-
.
|
|
1290
|
+
/* Symmetric on purpose. The list carried margin-top and nothing below, so whatever follows it —
|
|
1291
|
+
an "Add files" button, in every upload UI there is — sat flush against the last row: measured
|
|
1292
|
+
0px. A block that claims space on one side only is not spacing, it is a lean. */
|
|
1293
|
+
.fdy-filelist{display:flex;flex-direction:column;gap:var(--space-2);margin-top:var(--space-3);margin-bottom:var(--space-3);}
|
|
1294
|
+
/* Nothing after it, nothing to separate from — so the room is not spent. */
|
|
1295
|
+
.fdy-filelist:last-child{margin-bottom:0;}
|
|
1260
1296
|
/* Side-by-side rows on wide screens (reflow to one column when narrow). */
|
|
1261
1297
|
.fdy-filelist--grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));}
|
|
1262
1298
|
.fdy-file{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3);border:var(--bw) solid var(--color-border);border-radius:var(--radius-md);background:var(--color-surface);}
|
|
@@ -1768,6 +1804,13 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1768
1804
|
.fdy-table-bulkbar__spacer{flex:1;}
|
|
1769
1805
|
.fdy-table-bulkbar__actions{display:flex;gap:var(--space-2);}
|
|
1770
1806
|
|
|
1807
|
+
/* Controls inside a data row. `.fdy-input` and friends are width:100%, which in an auto-layout
|
|
1808
|
+
table means they contribute NO intrinsic width — the column shrinks to whatever the header text
|
|
1809
|
+
needs and the control collapses with it. A NOTE column ends up a box too narrow to read what you
|
|
1810
|
+
typed into it. The floor is per-control rather than on the column, because the table does not
|
|
1811
|
+
know which of its columns hold controls. */
|
|
1812
|
+
.fdy-table td>.fdy-input,.fdy-table td>.fdy-textarea,.fdy-table td>.fdy-combo,.fdy-table td>.fdy-input-group,.fdy-table td>.fdy-datepicker,.fdy-table td>.fdy-timepicker{min-width:7rem;}
|
|
1813
|
+
|
|
1771
1814
|
/* Freeday — Tabs (WAI-ARIA APG) */
|
|
1772
1815
|
/* position:relative — containing block for out-of-flow content in the tabs (see base.css). */
|
|
1773
1816
|
.fdy-tabs__list{position:relative;display:flex;gap:var(--space-1);border-bottom:var(--bw) solid var(--color-border);overflow-x:auto;}
|
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. */
|
|
@@ -706,6 +725,18 @@ a { color: var(--color-primary); }
|
|
|
706
725
|
.fdy-stat__value{font-family:var(--font-display);font-size:var(--text-3xl);font-weight:var(--weight-bold);letter-spacing:var(--tracking-tighter);line-height:1;color:var(--color-text);font-variant-numeric:tabular-nums;}
|
|
707
726
|
.fdy-stat__value small{font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text-muted);}
|
|
708
727
|
.fdy-stat__meta{font-size:var(--text-sm);color:var(--color-text-muted);display:flex;align-items:center;gap:var(--space-2);}
|
|
728
|
+
/* A stat value is display type sized for a COUNT — "1,284" — and a back office puts money in it.
|
|
729
|
+
"IDR 300,000.00" needs 224px at --text-3xl and the grid's own track is 11rem, so it wrapped
|
|
730
|
+
between the currency and the number: two lines of 31px where the design says one. Measured, not
|
|
731
|
+
guessed (#020).
|
|
732
|
+
The value shrinks only when its own column is too narrow to hold it, so a wide dashboard keeps
|
|
733
|
+
exactly the type it has today. Scoped to a stat INSIDE .fdy-stats, where the width comes from the
|
|
734
|
+
grid track — `container-type:inline-size` on a standalone .fdy-stat would stop it sizing to its
|
|
735
|
+
own content. The unconditional rule above stays as the fallback wherever @container is missing. */
|
|
736
|
+
@supports (container-type:inline-size){
|
|
737
|
+
.fdy-stats>.fdy-stat{container-type:inline-size;}
|
|
738
|
+
.fdy-stats>.fdy-stat .fdy-stat__value{font-size:clamp(var(--text-xl),11cqw,var(--text-3xl));}
|
|
739
|
+
}
|
|
709
740
|
.fdy-stats--boxed{background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);gap:0;}
|
|
710
741
|
.fdy-stats--boxed .fdy-stat{padding:var(--space-5);border-right:var(--bw) solid var(--color-border-muted);}
|
|
711
742
|
.fdy-stats--boxed .fdy-stat:last-child{border-right:0;}
|
|
@@ -873,7 +904,12 @@ a { color: var(--color-primary); }
|
|
|
873
904
|
.fdy-dropzone__text{display:flex;flex-direction:column;gap:var(--space-1);}
|
|
874
905
|
|
|
875
906
|
/* File rows (list of added/uploaded/rejected files) */
|
|
876
|
-
.
|
|
907
|
+
/* Symmetric on purpose. The list carried margin-top and nothing below, so whatever follows it —
|
|
908
|
+
an "Add files" button, in every upload UI there is — sat flush against the last row: measured
|
|
909
|
+
0px. A block that claims space on one side only is not spacing, it is a lean. */
|
|
910
|
+
.fdy-filelist{display:flex;flex-direction:column;gap:var(--space-2);margin-top:var(--space-3);margin-bottom:var(--space-3);}
|
|
911
|
+
/* Nothing after it, nothing to separate from — so the room is not spent. */
|
|
912
|
+
.fdy-filelist:last-child{margin-bottom:0;}
|
|
877
913
|
/* Side-by-side rows on wide screens (reflow to one column when narrow). */
|
|
878
914
|
.fdy-filelist--grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));}
|
|
879
915
|
.fdy-file{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3);border:var(--bw) solid var(--color-border);border-radius:var(--radius-md);background:var(--color-surface);}
|
|
@@ -1385,6 +1421,13 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1385
1421
|
.fdy-table-bulkbar__spacer{flex:1;}
|
|
1386
1422
|
.fdy-table-bulkbar__actions{display:flex;gap:var(--space-2);}
|
|
1387
1423
|
|
|
1424
|
+
/* Controls inside a data row. `.fdy-input` and friends are width:100%, which in an auto-layout
|
|
1425
|
+
table means they contribute NO intrinsic width — the column shrinks to whatever the header text
|
|
1426
|
+
needs and the control collapses with it. A NOTE column ends up a box too narrow to read what you
|
|
1427
|
+
typed into it. The floor is per-control rather than on the column, because the table does not
|
|
1428
|
+
know which of its columns hold controls. */
|
|
1429
|
+
.fdy-table td>.fdy-input,.fdy-table td>.fdy-textarea,.fdy-table td>.fdy-combo,.fdy-table td>.fdy-input-group,.fdy-table td>.fdy-datepicker,.fdy-table td>.fdy-timepicker{min-width:7rem;}
|
|
1430
|
+
|
|
1388
1431
|
/* Freeday — Tabs (WAI-ARIA APG) */
|
|
1389
1432
|
/* position:relative — containing block for out-of-flow content in the tabs (see base.css). */
|
|
1390
1433
|
.fdy-tabs__list{position:relative;display:flex;gap:var(--space-1);border-bottom:var(--bw) solid var(--color-border);overflow-x:auto;}
|