@cahyo-dimas/freeday 1.54.0 → 2.0.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 +625 -545
- package/COMPONENTS.md +141 -138
- package/README.id.md +46 -45
- package/README.md +46 -43
- package/USAGE.md +34 -34
- package/adapters/blazor/FdyAppShell.razor +2 -2
- package/adapters/blazor/FdyAppShell.razor.cs +6 -6
- package/adapters/blazor/FdyCfl.razor.cs +1 -1
- package/adapters/blazor/FdyChart.razor +1 -1
- package/adapters/blazor/FdyChart.razor.cs +3 -3
- package/adapters/blazor/FdyChartSeries.cs +1 -1
- package/adapters/blazor/FdyCombo.razor.cs +2 -2
- package/adapters/blazor/FdyModal.razor +1 -1
- package/adapters/blazor/FdyTable.razor.cs +7 -7
- package/adapters/blazor/FdyTableFilter.razor +1 -1
- package/adapters/blazor/FdyTableFooter.razor +1 -1
- package/adapters/blazor/FdyTableFooter.razor.cs +4 -4
- package/adapters/blazor/Freeday.Blazor.csproj +1 -1
- package/adapters/blazor/FreedayComponentBase.cs +1 -1
- package/adapters/blazor/TableModel.cs +3 -3
- package/adapters/blazor/TableTypes.cs +3 -3
- package/adapters/blazor/freeday-blazor.js +5 -5
- package/adapters/core/app-shell.d.ts +1 -1
- package/adapters/core/app-shell.js +6 -6
- package/adapters/core/table-model.d.ts +2 -2
- package/adapters/core/table-model.js +8 -8
- package/adapters/react/components/FdyAppShell.tsx +5 -5
- package/adapters/react/components/FdyAutocomplete.tsx +1 -1
- package/adapters/react/components/FdyCascade.tsx +1 -1
- package/adapters/react/components/FdyCfl.tsx +11 -11
- package/adapters/react/components/FdyDateRange.tsx +2 -2
- package/adapters/react/components/FdyDatepicker.tsx +5 -5
- package/adapters/react/components/FdyDrawer.tsx +1 -1
- package/adapters/react/components/FdyModal.tsx +2 -2
- package/adapters/react/components/FdyTable.tsx +7 -7
- package/adapters/react/components/FdyTableFilter.tsx +1 -1
- package/adapters/react/components/FdyTableFooter.tsx +5 -5
- package/adapters/react/index.d.ts +1 -1
- package/adapters/react/useFreeday.js +3 -3
- package/adapters/vue/components/FdyAppShell.vue +5 -5
- package/adapters/vue/components/FdyAutocomplete.vue +1 -1
- package/adapters/vue/components/FdyCascade.vue +1 -1
- package/adapters/vue/components/FdyCfl.vue +8 -8
- package/adapters/vue/components/FdyChart.vue +2 -2
- package/adapters/vue/components/FdyCombo.vue +1 -1
- package/adapters/vue/components/FdyDateRange.vue +2 -2
- package/adapters/vue/components/FdyDatepicker.vue +5 -5
- package/adapters/vue/components/FdyDrawer.vue +3 -3
- package/adapters/vue/components/FdyModal.vue +3 -3
- package/adapters/vue/components/FdyTable.vue +7 -7
- package/adapters/vue/components/FdyTableFilter.vue +1 -1
- package/adapters/vue/components/FdyTableFooter.vue +5 -5
- package/adapters/vue/index.d.ts +2 -2
- package/adapters/vue/useFreeday.js +3 -3
- package/dist/asset.d.ts +3 -3
- package/dist/freeday-app-shell.js +9 -9
- package/dist/freeday-autocomplete.js +2 -2
- package/dist/freeday-breakpoint.js +1 -1
- package/dist/freeday-carousel.js +5 -5
- package/dist/freeday-cascade.js +7 -7
- package/dist/freeday-cfl.js +9 -9
- package/dist/freeday-chart.js +11 -11
- package/dist/freeday-chip.js +3 -3
- package/dist/freeday-datepicker.js +11 -7
- package/dist/freeday-datetime.js +5 -5
- package/dist/freeday-drawer.js +1 -1
- package/dist/freeday-form.js +14 -14
- package/dist/freeday-mask.js +6 -6
- package/dist/freeday-menu.js +1 -1
- package/dist/freeday-number.js +3 -3
- package/dist/freeday-popover.js +3 -3
- package/dist/freeday-rating.js +2 -2
- package/dist/freeday-select.js +5 -5
- package/dist/freeday-slider.js +2 -2
- package/dist/freeday-stepper.js +7 -7
- package/dist/freeday-table.js +15 -15
- package/dist/freeday-tabs.js +1 -1
- package/dist/freeday-timepicker.js +2 -2
- package/dist/freeday-toast.js +8 -8
- package/dist/freeday-tree.js +3 -3
- package/dist/freeday-upload.js +25 -25
- package/dist/freeday.bundle.css +176 -176
- package/dist/freeday.css +170 -170
- package/dist/freeday.d.ts +2 -2
- package/dist/freeday.js +162 -158
- package/dist/freeday.tokens.css +5 -5
- package/docs/agent-onboarding.md +41 -41
- package/docs/getting-started.md +35 -35
- package/docs/integrations.md +18 -18
- package/docs/reference-screen.html +14 -14
- package/package.json +4 -3
- package/src/base.css +6 -6
- package/src/components/accordion.css +4 -4
- package/src/components/alert.css +1 -1
- package/src/components/app-shell.css +10 -10
- package/src/components/appbar.css +3 -3
- package/src/components/autocomplete.css +1 -1
- package/src/components/avatar.css +3 -3
- package/src/components/badge.css +5 -5
- package/src/components/breadcrumb.css +1 -1
- package/src/components/breakpoints.css +4 -4
- package/src/components/button.css +13 -13
- package/src/components/card.css +8 -8
- package/src/components/carousel.css +2 -2
- package/src/components/cascade.css +2 -2
- package/src/components/cfl.css +4 -4
- package/src/components/chart.css +3 -3
- package/src/components/chip.css +2 -2
- package/src/components/combo.css +3 -3
- package/src/components/composition.css +7 -7
- package/src/components/datepicker.css +6 -6
- package/src/components/datetimepicker.css +1 -1
- package/src/components/description-list.css +1 -1
- package/src/components/divider.css +1 -1
- package/src/components/drawer.css +1 -1
- package/src/components/file-upload.css +5 -5
- package/src/components/filterbar.css +7 -7
- package/src/components/form-grid.css +2 -2
- package/src/components/input-group.css +6 -6
- package/src/components/input.css +6 -6
- package/src/components/kbd.css +1 -1
- package/src/components/list.css +6 -6
- package/src/components/menu.css +3 -3
- package/src/components/modal.css +1 -1
- package/src/components/pagination.css +1 -1
- package/src/components/progress.css +1 -1
- package/src/components/rating.css +2 -2
- package/src/components/selection.css +4 -4
- package/src/components/skeleton.css +1 -1
- package/src/components/slider.css +1 -1
- package/src/components/spinner.css +1 -1
- package/src/components/states.css +1 -1
- package/src/components/stepper.css +2 -2
- package/src/components/table.css +15 -15
- package/src/components/tabs.css +3 -3
- package/src/components/timeline.css +1 -1
- package/src/components/timepicker.css +1 -1
- package/src/components/toast.css +3 -3
- package/src/components/tooltip.css +1 -1
- package/src/components/tree.css +2 -2
- package/tokens/breakpoints.d.ts +2 -2
- package/tokens/breakpoints.mjs +3 -3
- package/tokens/tokens.json +1 -1
|
@@ -9,7 +9,7 @@ import { computed, nextTick, onBeforeUnmount, ref, useId, type ComputedRef, type
|
|
|
9
9
|
// that: a real `v-model:Row|null` over a native <dialog> whose rows come from an async
|
|
10
10
|
// `fetchPage(query, page)`. It re-implements the enhancer's open/close + search + dense
|
|
11
11
|
// sticky-header results + single-commit-on-click + keyboard, and adds the pieces a server
|
|
12
|
-
// picker needs
|
|
12
|
+
// picker needs, loading/empty/error states, retry, pagination, an out-of-order guard, and
|
|
13
13
|
// an optional in-memory cache. The enhancer is NOT mounted here (it would fight Vue's DOM).
|
|
14
14
|
|
|
15
15
|
interface CflColumn {
|
|
@@ -23,7 +23,7 @@ interface CflPage {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const props = defineProps<{
|
|
26
|
-
/** Single: `Row | null`. With `multiple`, an array
|
|
26
|
+
/** Single: `Row | null`. With `multiple`, an array, `Row[] | null`, where null and [] both mean
|
|
27
27
|
* nothing picked. The enhancer has had `data-fdy-cfl-multiple` all along; this is the typed
|
|
28
28
|
* wrappers catching up (#019). */
|
|
29
29
|
modelValue: Row | Row[] | null;
|
|
@@ -31,7 +31,7 @@ const props = defineProps<{
|
|
|
31
31
|
columns: ReadonlyArray<CflColumn>;
|
|
32
32
|
display: (row: Row) => string;
|
|
33
33
|
rowKey: (row: Row) => string;
|
|
34
|
-
/** Advisory only
|
|
34
|
+
/** Advisory only, the caller's `fetchPage` owns paging; kept for API documentation. */
|
|
35
35
|
pageSize?: number;
|
|
36
36
|
/** The dialog's heading. Default 'Choose data', matching the Blazor adapter's `Title`. */
|
|
37
37
|
title?: string;
|
|
@@ -108,7 +108,7 @@ const showClear: ComputedRef<boolean> = computed(
|
|
|
108
108
|
);
|
|
109
109
|
const clearLabelText: ComputedRef<string> = computed((): string => props.clearLabel ?? 'Clear selection');
|
|
110
110
|
const isInvalid: ComputedRef<boolean> = computed((): boolean => props.invalid === true);
|
|
111
|
-
/* `display()` takes one row, so in multi the field states HOW MANY
|
|
111
|
+
/* `display()` takes one row, so in multi the field states HOW MANY, naming one of six would be a
|
|
112
112
|
lie, and naming all six does not fit a control that is 22rem wide. */
|
|
113
113
|
const currentRows: ComputedRef<Row[]> = computed((): Row[] =>
|
|
114
114
|
Array.isArray(props.modelValue) ? props.modelValue : props.modelValue !== null ? [props.modelValue as Row] : [],
|
|
@@ -119,7 +119,7 @@ const displayValue: ComputedRef<string> = computed((): string => {
|
|
|
119
119
|
return props.modelValue !== null ? props.display(props.modelValue as Row) : '';
|
|
120
120
|
});
|
|
121
121
|
// The results <table> (owner of `resultsId`) only renders in the rows branch, so gate the
|
|
122
|
-
// search input's aria refs on rows existing
|
|
122
|
+
// search input's aria refs on rows existing, otherwise they'd dangle during loading/empty/error.
|
|
123
123
|
const hasRows: ComputedRef<boolean> = computed((): boolean => rows.value.length > 0);
|
|
124
124
|
const controlsId: ComputedRef<string | undefined> = computed((): string | undefined =>
|
|
125
125
|
hasRows.value ? resultsId : undefined,
|
|
@@ -156,7 +156,7 @@ async function loadPage(targetPage: number, append: boolean): Promise<void> {
|
|
|
156
156
|
try {
|
|
157
157
|
const cached: CflPage | undefined = cache.get(key);
|
|
158
158
|
const res: CflPage = cached ?? (await props.fetchPage(q, targetPage));
|
|
159
|
-
if (token !== reqToken) return; // stale
|
|
159
|
+
if (token !== reqToken) return; // stale, a newer request has started
|
|
160
160
|
if (cached === undefined) cache.set(key, res);
|
|
161
161
|
const copy: Row[] = res.rows.slice(); // never mutate the caller's array
|
|
162
162
|
rows.value = append ? rows.value.concat(copy) : copy;
|
|
@@ -220,7 +220,7 @@ function togglePick(row: Row): void {
|
|
|
220
220
|
else picked.value = picked.value.filter((_: Row, i: number): boolean => i !== at);
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
/* A click means "tick this" in multi and "this is my answer" in single
|
|
223
|
+
/* A click means "tick this" in multi and "this is my answer" in single, the whole difference. */
|
|
224
224
|
function onRowClick(row: Row): void {
|
|
225
225
|
if (props.multiple === true) togglePick(row);
|
|
226
226
|
else commit(row);
|
|
@@ -238,7 +238,7 @@ function commit(row: Row | null): void {
|
|
|
238
238
|
closeDialog();
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
/* Unsetting is not "picking nothing"
|
|
241
|
+
/* Unsetting is not "picking nothing", it must not open or close the dialog, and it must leave focus
|
|
242
242
|
on a control that still exists, so focus returns to the trigger beside it. */
|
|
243
243
|
function clearValue(): void {
|
|
244
244
|
picked.value = [];
|
|
@@ -4,7 +4,7 @@ import { computed, onBeforeUnmount, onMounted, ref, watch, nextTick, type Comput
|
|
|
4
4
|
// A Vue-native wrapper over freeday's zero-dependency chart renderer (freeday-chart.js).
|
|
5
5
|
// Unlike the input components, charts are not form controls, so re-implementing them in Vue
|
|
6
6
|
// would only duplicate the SVG maths. Instead this thin wrapper binds the renderer's data-*
|
|
7
|
-
// API reactively and re-invokes window.FreedayChart.update() whenever the data changes
|
|
7
|
+
// API reactively and re-invokes window.FreedayChart.update() whenever the data changes, giving
|
|
8
8
|
// reactive, typed charts (a drop-in replacement for hand-rolled Chart.js wrappers) with no
|
|
9
9
|
// external dependency. Because the renderer paints with CSS var()/--chart-* tokens, charts also
|
|
10
10
|
// recolour on data-theme change for free. Requires the freeday enhancers (dist/freeday.js) to be
|
|
@@ -16,7 +16,7 @@ interface FdyChartSeries {
|
|
|
16
16
|
role?: string;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
// Note: no `ariaLabel` prop
|
|
19
|
+
// Note: no `ariaLabel` prop, pass `aria-label` (and any other native attribute) directly; it
|
|
20
20
|
// falls through onto the root element. `role="img"` is set below. Give every chart an aria-label.
|
|
21
21
|
const props = defineProps<{
|
|
22
22
|
type: 'line' | 'area' | 'bar' | 'sparkline' | 'donut';
|
|
@@ -220,7 +220,7 @@ onBeforeUnmount((): void => {
|
|
|
220
220
|
<ul :id="listboxId" ref="listboxEl" class="fdy-combo__listbox" role="listbox" popover="manual" :hidden="!open">
|
|
221
221
|
<!-- @mousedown.prevent keeps focus on the button: the option <li> isn't focusable, so a plain
|
|
222
222
|
mousedown moves focus out of the combo, fires @focusout, and closes the list before the click
|
|
223
|
-
lands
|
|
223
|
+
lands, mouse-select would silently do nothing. Same pattern as FdyDatepicker/FdyAutocomplete. -->
|
|
224
224
|
<li
|
|
225
225
|
v-for="(opt, i) in options"
|
|
226
226
|
:id="optionId(i)"
|
|
@@ -13,7 +13,7 @@ import FdyDatepicker from './FdyDatepicker.vue';
|
|
|
13
13
|
// two linked FdyDatepickers where the end can't precede the start (start.max = end, end.min =
|
|
14
14
|
// start). A real `v-model:DateRangeValue` that composes the single-date picker rather than
|
|
15
15
|
// re-implementing the calendar. The vanilla enhancer's cross-calendar `.in-range` day shading is
|
|
16
|
-
// not reproduced (each picker is independent)
|
|
16
|
+
// not reproduced (each picker is independent), the min/max linkage is what keeps the range valid.
|
|
17
17
|
|
|
18
18
|
const props = defineProps<{
|
|
19
19
|
modelValue: DateRangeValue;
|
|
@@ -21,7 +21,7 @@ const props = defineProps<{
|
|
|
21
21
|
max?: string;
|
|
22
22
|
locale?: string;
|
|
23
23
|
disabled?: boolean;
|
|
24
|
-
/** Locked/view mode for both pickers
|
|
24
|
+
/** Locked/view mode for both pickers, focusable, values shown, but can't be opened or changed. */
|
|
25
25
|
readonly?: boolean;
|
|
26
26
|
invalid?: boolean;
|
|
27
27
|
describedby?: string;
|
|
@@ -39,7 +39,7 @@ const props = defineProps<{
|
|
|
39
39
|
ariaLabelledby?: string;
|
|
40
40
|
/** Show a clear (×) button in the trigger when a date is set, so an optional date can be unset. Emits `''` via update:modelValue + change. Off by default. */
|
|
41
41
|
clearable?: boolean;
|
|
42
|
-
/** aria-label for the previous-month nav button. Default 'Previous month'
|
|
42
|
+
/** aria-label for the previous-month nav button. Default 'Previous month', override for non-English UIs (month/weekday names already follow `locale`). */
|
|
43
43
|
prevMonthLabel?: string;
|
|
44
44
|
/** aria-label for the next-month nav button. Default 'Next month'. */
|
|
45
45
|
nextMonthLabel?: string;
|
|
@@ -145,7 +145,7 @@ const effectiveFocusDate: ComputedRef<Date> = computed((): Date => {
|
|
|
145
145
|
return new Date(view.value.getFullYear(), view.value.getMonth(), Math.min(f.getDate(), daysInMonth));
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
-
/* 'days' | 'months'
|
|
148
|
+
/* 'days' | 'months', the calendar drills one level up instead of growing furniture beside the title.
|
|
149
149
|
Before this the only pointer route to another month was one click per month: August 2026 to March
|
|
150
150
|
2022 is fifty-three of them, and the Shift+PageUp jump had no affordance at all. */
|
|
151
151
|
const mode: Ref<'days' | 'months' | 'years'> = ref('days');
|
|
@@ -212,7 +212,7 @@ interface YearCell {
|
|
|
212
212
|
focusable: boolean;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
/** A year is unreachable only when EVERY month in it falls outside min/max
|
|
215
|
+
/** A year is unreachable only when EVERY month in it falls outside min/max, the same rule
|
|
216
216
|
* isMonthDisabled applies one level down, and for the same reason. */
|
|
217
217
|
function isYearDisabled(year: number): boolean {
|
|
218
218
|
if (minDate.value !== null && startOfDay(new Date(year, 11, 31)).getTime() < startOfDay(minDate.value).getTime()) return true;
|
|
@@ -411,7 +411,7 @@ function pick(d: Date): void {
|
|
|
411
411
|
closePanel(true);
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
-
// Clear (reset to empty). Emits ''
|
|
414
|
+
// Clear (reset to empty). Emits '', parseISO('') is null, so the placeholder shows again.
|
|
415
415
|
function clearValue(): void {
|
|
416
416
|
emit('update:modelValue', '');
|
|
417
417
|
emit('change', '');
|
|
@@ -496,7 +496,7 @@ function onDocPointerDown(e: MouseEvent): void {
|
|
|
496
496
|
}
|
|
497
497
|
|
|
498
498
|
// Close when focus leaves the control entirely (e.g. Shift+Tab off the trigger).
|
|
499
|
-
/* A null relatedTarget means focus fell to <body
|
|
499
|
+
/* A null relatedTarget means focus fell to <body>, which is what happens when the element the
|
|
500
500
|
* user just pressed is REMOVED by the click it triggered (drilling into the month grid replaces
|
|
501
501
|
* the grid, and with it the cell that had focus). That is not focus leaving the control, and
|
|
502
502
|
* closing on it made the panel vanish mid-navigation. A pointer that really lands outside is
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
import { computed, onMounted, useId, watch, type ComputedRef, type Ref, ref } from 'vue';
|
|
3
3
|
|
|
4
4
|
// A controlled Vue wrapper over freeday's `.fdy-drawer` native <dialog> side panel
|
|
5
|
-
// (src/components/drawer.css). Same controlled contract and glue as FdyModal
|
|
5
|
+
// (src/components/drawer.css). Same controlled contract and glue as FdyModal, showModal()/close()
|
|
6
6
|
// guarded, @cancel.prevent so Esc routes through app state, backdrop-click via `event.target ===
|
|
7
|
-
// dialogEl
|
|
7
|
+
// dialogEl`, applied to a drawer that anchors left (default) or right. Native <dialog> supplies the
|
|
8
8
|
// focus trap, focus restore, top-layer stacking and inert background; `dismissible` (default true)
|
|
9
9
|
// gates Esc + backdrop dismissal.
|
|
10
10
|
|
|
11
11
|
// dismissible MUST go through withDefaults: Vue's boolean-cast gives an omitted Boolean prop
|
|
12
12
|
// `false`, not `undefined`, so a bare `props.dismissible !== false` would make an un-annotated
|
|
13
|
-
// drawer non-dismissible (no Esc, no backdrop, no close button)
|
|
13
|
+
// drawer non-dismissible (no Esc, no backdrop, no close button), the opposite of the default.
|
|
14
14
|
const props = withDefaults(defineProps<{
|
|
15
15
|
open: boolean;
|
|
16
16
|
title: string;
|
|
@@ -8,11 +8,11 @@ import { computed, onMounted, useId, watch, type ComputedRef, type Ref, ref } fr
|
|
|
8
8
|
// (showModal() on an open dialog throws), @cancel.prevent so Esc routes through app state instead of
|
|
9
9
|
// closing the DOM behind its back, and backdrop-click detection via `event.target === dialogEl`.
|
|
10
10
|
// Native <dialog> already provides the focus trap, focus restore, top-layer stacking and inert
|
|
11
|
-
// background
|
|
11
|
+
// background, the wrapper only avoids breaking them. `dismissible` (default true) gates Esc + backdrop.
|
|
12
12
|
|
|
13
13
|
// dismissible MUST go through withDefaults: Vue's boolean-cast gives an omitted Boolean prop
|
|
14
14
|
// `false`, not `undefined`, so a bare `props.dismissible !== false` would make an un-annotated
|
|
15
|
-
// modal non-dismissible (no Esc, no backdrop, no close button)
|
|
15
|
+
// modal non-dismissible (no Esc, no backdrop, no close button), the opposite of the default.
|
|
16
16
|
const props = withDefaults(defineProps<{
|
|
17
17
|
open: boolean;
|
|
18
18
|
title: string;
|
|
@@ -46,7 +46,7 @@ onMounted((): void => sync(props.open));
|
|
|
46
46
|
function onCancel(): void {
|
|
47
47
|
if (dismissible.value) emit('close');
|
|
48
48
|
}
|
|
49
|
-
// The ::backdrop is not a separate element
|
|
49
|
+
// The ::backdrop is not a separate element, a click whose target is the dialog box itself (not its
|
|
50
50
|
// content) is a backdrop click.
|
|
51
51
|
function onClick(e: MouseEvent): void {
|
|
52
52
|
if (dismissible.value && e.target === dialogEl.value) emit('close');
|
|
@@ -45,14 +45,14 @@ const props = withDefaults(defineProps<{
|
|
|
45
45
|
/** Client-side page size when `page` is absent; 0/undefined = render all rows (no pager). */
|
|
46
46
|
pageSize?: number;
|
|
47
47
|
/**
|
|
48
|
-
* Controlled client-side page index (0-based). Provide it
|
|
48
|
+
* Controlled client-side page index (0-based). Provide it, with `pageSize`, without `page`, to
|
|
49
49
|
* own the page while the table keeps doing filter/sort/paginate. This is what lets an EXTERNAL
|
|
50
50
|
* pager drive the table: a responsive screen that hides `.fdy-datatable` below `md` and renders a
|
|
51
51
|
* card list from the `process` event can render one pager for both breakpoints and point it here.
|
|
52
52
|
* Omit for the internal index (unchanged default).
|
|
53
53
|
*/
|
|
54
54
|
/** Withhold the table's own footer (pager + range) so the screen can render one. Server mode had
|
|
55
|
-
* no way to do this: the app owns the page there ANYWAY, and was still handed a second control
|
|
55
|
+
* no way to do this: the app owns the page there ANYWAY, and was still handed a second control,
|
|
56
56
|
* a responsive list that shows a table at one breakpoint and cards at another ended up with the
|
|
57
57
|
* kit's pager stacked under its own. Client mode's counterpart is `pageIndex`. Default true. */
|
|
58
58
|
pager?: boolean;
|
|
@@ -61,7 +61,7 @@ const props = withDefaults(defineProps<{
|
|
|
61
61
|
* (unchanged default). Every back office has one, and a table that renders two thirds of its own
|
|
62
62
|
* footer forces the app to rebuild all three to add the last (#008).
|
|
63
63
|
*
|
|
64
|
-
* Server mode reports the pick through `update:page
|
|
64
|
+
* Server mode reports the pick through `update:page`, same event as a page click, with a new
|
|
65
65
|
* `size`. Client mode keeps it internally and also emits `update:pageSize`, so the control works
|
|
66
66
|
* with nothing wired and can still be persisted.
|
|
67
67
|
*/
|
|
@@ -86,11 +86,11 @@ const emit = defineEmits<{
|
|
|
86
86
|
* a reset to 0 after sort/filter, or a clamp when filtering shrank the set). */
|
|
87
87
|
'update:pageIndex': [index: number];
|
|
88
88
|
/** Client mode with `pageSizes`: the reader picked a new rows-per-page. The table has already
|
|
89
|
-
* applied it
|
|
89
|
+
* applied it, this is for a caller that wants to persist the choice. */
|
|
90
90
|
'update:pageSize': [size: number];
|
|
91
91
|
/** A row was activated (click, or Enter/Space while the row itself is focused). */
|
|
92
92
|
'row-activate': [row: Row];
|
|
93
|
-
/** The processed page of rows (after filter/sort/paginate) plus the total row count
|
|
93
|
+
/** The processed page of rows (after filter/sort/paginate) plus the total row count, fires in
|
|
94
94
|
* BOTH modes whenever they change. Lets a consumer render the SAME processed set elsewhere
|
|
95
95
|
* (a `< md` card list, a "selected" summary, export-to-CSV) without re-deriving the pipeline. */
|
|
96
96
|
'process': [result: { rows: Row[]; total: number }];
|
|
@@ -100,7 +100,7 @@ const internalSort: Ref<FdySortState | null> = ref(null);
|
|
|
100
100
|
const internalFilters: Ref<FdyFilterMap> = ref({});
|
|
101
101
|
const internalPageIndex: Ref<number> = ref(0);
|
|
102
102
|
/* Client-mode rows-per-page. `pageSize` is a plain prop with no event, so a footer control that only
|
|
103
|
-
* emitted would do nothing in the app that wired nothing
|
|
103
|
+
* emitted would do nothing in the app that wired nothing, the table applies the pick itself and
|
|
104
104
|
* reports it. An explicit change to the prop wins back: the parent said something newer. */
|
|
105
105
|
const internalPageSize: Ref<number | null> = ref(null);
|
|
106
106
|
watch((): number | undefined => props.pageSize, (): void => { internalPageSize.value = null; });
|
|
@@ -250,7 +250,7 @@ function rowClasses(row: Row): Array<string | undefined> {
|
|
|
250
250
|
function onRowClick(row: Row): void {
|
|
251
251
|
if (props.rowActivatable === true) emit('row-activate', row);
|
|
252
252
|
}
|
|
253
|
-
// Enter/Space activate only when the row itself is focused
|
|
253
|
+
// Enter/Space activate only when the row itself is focused, a control inside a cell keeps its own
|
|
254
254
|
// event (the `event.target !== event.currentTarget` guard). Click relies on inner controls calling
|
|
255
255
|
// stopPropagation, matching the pattern consumers hand-roll today.
|
|
256
256
|
function onRowKeydown(e: KeyboardEvent, row: Row): void {
|
|
@@ -7,7 +7,7 @@ import type { FdyColumnFilter, FdyColumnFilterType } from '../../core/table-mode
|
|
|
7
7
|
// Internal to FdyTable: one column's header funnel button + its type-aware filter popover
|
|
8
8
|
// (text / enum / number / date) over freeday's `.fdy-table__filterbtn` + `.fdy-filter*` classes.
|
|
9
9
|
// Reuses usePopover so the panel escapes the table's `overflow:hidden` via the top layer. Purely
|
|
10
|
-
// controlled
|
|
10
|
+
// controlled, it renders the current `filter` and emits the next one (or null to clear); the
|
|
11
11
|
// parent owns where that goes (client state or an `update:filters` emit). Not exported.
|
|
12
12
|
|
|
13
13
|
const props = defineProps<{
|
|
@@ -8,19 +8,19 @@ import FdyCombo from './FdyCombo.vue';
|
|
|
8
8
|
* The band under a table: what you are looking at, how much of it you see, where you are.
|
|
9
9
|
*
|
|
10
10
|
* `FdyTable` renders this itself, so most screens never import it. It is exported for the one shape
|
|
11
|
-
* that cannot use the table's own
|
|
11
|
+
* that cannot use the table's own, a RESPONSIVE list, where a `.fdy-datatable` at `lg` and a
|
|
12
12
|
* `.fdy-list` below it are two renderings of ONE page of rows. A footer inside the table is inside
|
|
13
13
|
* the half that is hidden on a phone, so those screens need to render it once, outside both
|
|
14
14
|
* (improvement notes #005 and #008, from IDU_EMATE_APPL_WEB).
|
|
15
15
|
*
|
|
16
|
-
* It owns nothing. `page` in, `update:page` out
|
|
16
|
+
* It owns nothing. `page` in, `update:page` out, the same contract as the table's server mode, so a
|
|
17
17
|
* screen already wired for that switches by moving the prop.
|
|
18
18
|
*/
|
|
19
19
|
const props = defineProps<{
|
|
20
20
|
/** The page being shown. `size` drives the range AND the rows-per-page control's value. */
|
|
21
21
|
page: FdyPageState;
|
|
22
22
|
/**
|
|
23
|
-
* Offer a rows-per-page control. Omit for none
|
|
23
|
+
* Offer a rows-per-page control. Omit for none; the footer is then range + pager, exactly as
|
|
24
24
|
* before. Picking a size emits `update:page` carrying the new `size` and the index that still
|
|
25
25
|
* holds the row you were looking at.
|
|
26
26
|
*/
|
|
@@ -29,7 +29,7 @@ const props = defineProps<{
|
|
|
29
29
|
|
|
30
30
|
const emit = defineEmits<{ 'update:page': [page: FdyPageState] }>();
|
|
31
31
|
|
|
32
|
-
/* The combo is labelled by the visible word beside it
|
|
32
|
+
/* The combo is labelled by the visible word beside it, a `<label for>` cannot reach inside a
|
|
33
33
|
* component, and an aria-label would leave that word unattached to anything. */
|
|
34
34
|
const sizeLabelId: string = `fdy-rows-${useId()}`;
|
|
35
35
|
|
|
@@ -54,7 +54,7 @@ const sizes: ComputedRef<readonly number[]> = computed((): readonly number[] =>
|
|
|
54
54
|
const sizeOptions: ComputedRef<Array<{ value: string; label: string }>> = computed(() =>
|
|
55
55
|
sizes.value.map((size: number) => ({ value: String(size), label: String(size) })),
|
|
56
56
|
);
|
|
57
|
-
/* One page and no size control means there is nothing here to say
|
|
57
|
+
/* One page and no size control means there is nothing here to say, the table has always withheld
|
|
58
58
|
* the whole band in that case, and this is where that decision now lives. */
|
|
59
59
|
const visible: ComputedRef<boolean> = computed((): boolean => hasPager.value || sizes.value.length > 0);
|
|
60
60
|
|
package/adapters/vue/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare function useFreeday(rootRef?: Ref<HTMLElement | null>): UseFreeda
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Anchor a dropdown panel to its trigger in the top layer, so it escapes any ancestor overflow clip
|
|
17
|
-
|
|
17
|
+
*, `.fdy-card` is `overflow:hidden`, so a panel positioned inside one is otherwise cut at the card's
|
|
18
18
|
* edge. The panel element must carry `popover="manual"`. This is the plumbing every kit dropdown
|
|
19
19
|
* uses; it is exported so a control the kit does not ship can behave like one that it does, instead
|
|
20
20
|
* of each app re-implementing it from the description.
|
|
@@ -35,7 +35,7 @@ export type { CascadeNode } from './components/FdyCascade.vue';
|
|
|
35
35
|
export { default as FdyCfl } from './components/FdyCfl.vue';
|
|
36
36
|
export { default as FdyChart } from './components/FdyChart.vue';
|
|
37
37
|
export { default as FdyTable } from './components/FdyTable.vue';
|
|
38
|
-
/** The table's own footer, standalone
|
|
38
|
+
/** The table's own footer, standalone, for a responsive list whose table is hidden at some
|
|
39
39
|
* breakpoints and so cannot host it. */
|
|
40
40
|
export { default as FdyTableFooter } from './components/FdyTableFooter.vue';
|
|
41
41
|
export { default as FdyModal } from './components/FdyModal.vue';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Freeday
|
|
1
|
+
/* Freeday. Vue 3 adapter (thin). The enhancers stay the source of truth; this
|
|
2
2
|
* composable just re-runs every registered window.Freeday*.initAll() over a
|
|
3
3
|
* subtree after Vue renders it, so [data-fdy-*] markup rendered by Vue gets
|
|
4
4
|
* enhanced (and re-enhanced after updates). Enhancers are idempotent, so the
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
* useFreeday(root); // scopes init to this component's subtree
|
|
11
11
|
* // <div ref="root"> ...[data-fdy-*] markup... </div>
|
|
12
12
|
*
|
|
13
|
-
* The ref may also sit ON the widget itself
|
|
13
|
+
* The ref may also sit ON the widget itself, <div ref="root" data-fdy-menu>, which is what you
|
|
14
14
|
* write when the component's root element IS the widget. initAll() matches its own root as
|
|
15
15
|
* well as descendants, so both shapes work. (Before 1.23.0 only the wrapping shape did, and
|
|
16
16
|
* the other failed silently: querySelectorAll never matches its root.)
|
|
17
17
|
*
|
|
18
|
-
* Events come straight through as native bubbling CustomEvents
|
|
18
|
+
* Events come straight through as native bubbling CustomEvents, bind them with
|
|
19
19
|
* plain v-on (e.g. @fdy-cascade-change) and read event.detail. See index.d.ts
|
|
20
20
|
* for the detail types.
|
|
21
21
|
*/
|
package/dist/asset.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// Shared type stub for every export imported purely for its side effects
|
|
1
|
+
// Shared type stub for every export imported purely for its side effects: the CSS
|
|
2
2
|
// bundles (`./css`, `./css/components`, `./tokens`) and the individual enhancer scripts
|
|
3
3
|
// (`./enhancers/*`). TypeScript only needs the `types` condition to *resolve* the module;
|
|
4
|
-
// there is no value surface to describe. (The root `.` import is different
|
|
5
|
-
// window.Freeday
|
|
4
|
+
// there is no value surface to describe. (The root `.` import is different, since it augments
|
|
5
|
+
// window.Freeday, so it points at ./freeday.d.ts instead.)
|
|
6
6
|
export {};
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
/* Freeday
|
|
1
|
+
/* Freeday, app shell enhancer (optional, zero-dependency).
|
|
2
2
|
* Opt in with <div class="fdy-app" data-fdy-app>; the markup is otherwise unchanged.
|
|
3
3
|
*
|
|
4
4
|
* The shell has always shipped the state classes and no behaviour, which left every consumer to
|
|
5
|
-
* assemble the toggle, Escape, focus trap, focus restore and `inert` themselves
|
|
5
|
+
* assemble the toggle, Escape, focus trap, focus restore and `inert` themselves, and the two
|
|
6
6
|
* hand-rolled copies in this repo's own docs already disagreed about which of those exist. Below
|
|
7
7
|
* 721px the sidebar is an OVERLAY (app-shell.css: position:fixed + translateX(-100%)), so it needs
|
|
8
8
|
* the same treatment a modal drawer gets; at or above 721px it is a column that merely collapses.
|
|
9
9
|
*
|
|
10
10
|
* One rule covers both a hidden nav and a hidden page:
|
|
11
11
|
* - `__sidebar` is inert whenever the nav is NOT visible. Off-canvas and collapsed panels stay in
|
|
12
|
-
* the tab order otherwise
|
|
12
|
+
* the tab order otherwise, translateX(-100%) and width:0 hide a thing from the eye, not from
|
|
13
13
|
* the keyboard, which is how a nav nobody can see still swallows Tab.
|
|
14
14
|
* - `__content` is inert only while the nav is an OPEN OVERLAY, so Tab cannot wander behind the
|
|
15
15
|
* backdrop.
|
|
16
16
|
*
|
|
17
17
|
* Emits a bubbling `fdy-app-nav` CustomEvent (detail {visible}) whenever the nav's visibility
|
|
18
18
|
* changes, and takes `FreedayAppShell.setVisible(root, visible)` from outside. Those two exist for
|
|
19
|
-
* the same reason the other enhancers have them: a host that keeps its own state
|
|
20
|
-
* wrapper binding @bind-NavOpen, an app persisting the collapsed preference
|
|
19
|
+
* the same reason the other enhancers have them: a host that keeps its own state, the Blazor
|
|
20
|
+
* wrapper binding @bind-NavOpen, an app persisting the collapsed preference, has to be able to
|
|
21
21
|
* hear the change and to drive it, without owning the behaviour twice.
|
|
22
22
|
* FreedayAppShell.init(root) for late-mounted markup.
|
|
23
23
|
*/
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
+ ' textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
32
32
|
|
|
33
33
|
/* getClientRects(), not offsetParent: an overlay sidebar is position:fixed, and a fixed element
|
|
34
|
-
reports no offsetParent at all
|
|
34
|
+
reports no offsetParent at all, filtering on it would call every nav item invisible. */
|
|
35
35
|
function focusables(root) {
|
|
36
36
|
return Array.prototype.filter.call(root.querySelectorAll(FOCUSABLE), function (el) {
|
|
37
37
|
return el.getClientRects().length > 0;
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
function isCollapsed() { return app.classList.contains('fdy-app--nav-collapsed'); }
|
|
63
63
|
function navVisible() { return mqWide.matches ? !isCollapsed() : isOverlayOpen(); }
|
|
64
64
|
|
|
65
|
-
/* aria-expanded answers "is the nav showing?" in BOTH modes
|
|
65
|
+
/* aria-expanded answers "is the nav showing?" in BOTH modes, the two state classes are the
|
|
66
66
|
kit's business, not the reader's. */
|
|
67
67
|
function sync() {
|
|
68
68
|
var visible = navVisible();
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
/* restoreFocus is false when the viewport closed it rather than the user
|
|
94
|
+
/* restoreFocus is false when the viewport closed it rather than the user, a resize must not
|
|
95
95
|
yank focus out from under whatever the reader was doing. */
|
|
96
96
|
function close(restoreFocus) {
|
|
97
97
|
if (!isOverlayOpen()) return;
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
|
|
119
119
|
if (backdrop) backdrop.addEventListener('click', function () { close(); });
|
|
120
120
|
|
|
121
|
-
/* Following a link inside an overlay nav means "take me there"
|
|
121
|
+
/* Following a link inside an overlay nav means "take me there", leaving the panel open over
|
|
122
122
|
the page you just asked for is the one thing every hand-rolled copy in this repo disagreed on. */
|
|
123
123
|
sidebar.addEventListener('click', function (e) {
|
|
124
124
|
if (!isOverlayOpen()) return;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/* Freeday
|
|
1
|
+
/* Freeday, autocomplete enhancer (optional, zero-dependency).
|
|
2
2
|
* Editable combobox (WAI-ARIA APG): a text input filters a role="listbox" of role="option"
|
|
3
3
|
* items as you type; select fills the input. Auto-inits [data-fdy-autocomplete]. Options are
|
|
4
|
-
* static markup filtered client-side
|
|
4
|
+
* static markup filtered client-side, in an app, fetch/filter server-side and re-render options.
|
|
5
5
|
*
|
|
6
6
|
* Markup: <div data-fdy-autocomplete class="fdy-autocomplete">
|
|
7
7
|
* <input class="fdy-input" role="combobox" aria-expanded="false" aria-autocomplete="list"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Freeday
|
|
1
|
+
/* Freeday, breakpoint provider (optional, zero-dependency).
|
|
2
2
|
* The MudBreakpointProvider equivalent for JS consumers: stamps the current breakpoint on
|
|
3
3
|
* <html data-breakpoint="xs|sm|md|lg|xl"> and emits a bubbling "fdy-breakpoint-change"
|
|
4
4
|
* CustomEvent (detail {breakpoint}) whenever it changes. Also fills any element with
|
package/dist/freeday-carousel.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Freeday
|
|
1
|
+
/* Freeday, carousel enhancer (optional, zero-dependency).
|
|
2
2
|
* Scroll-snap slider with prev/next arrows, generated dot indicators, keyboard (←/→),
|
|
3
3
|
* and optional autoplay (data-fdy-autoplay="4000", pauses on hover/focus). Auto-inits
|
|
4
4
|
* [data-fdy-carousel].
|
|
@@ -18,16 +18,16 @@
|
|
|
18
18
|
'use strict';
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
/* User-facing strings. Indonesian by default
|
|
22
|
-
* path
|
|
21
|
+
/* User-facing strings. Indonesian by default, documented and deliberate for the raw enhancer
|
|
22
|
+
* path, and every one overridable per element, so a host that speaks another language (the
|
|
23
23
|
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
24
24
|
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
25
25
|
var TEXT = {
|
|
26
|
-
position: '{n}
|
|
26
|
+
position: '{n} of {total}',
|
|
27
27
|
slide: 'Slide {n}'
|
|
28
28
|
};
|
|
29
29
|
/* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
|
|
30
|
-
as `data-fdy-text-filtertext
|
|
30
|
+
as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
|
|
31
31
|
`data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
|
|
32
32
|
override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
|
|
33
33
|
still resolves, for markup written against 1.39.0. */
|
package/dist/freeday-cascade.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Freeday
|
|
1
|
+
/* Freeday, cascade select enhancer (optional, zero-dependency).
|
|
2
2
|
* Turns a [data-fdy-cascade] wrapper into a hierarchical drill-down picker. The
|
|
3
3
|
* data model is a hidden nested <ul> inside the wrapper: a <li> with a child <ul>
|
|
4
4
|
* is a branch (drills in), a <li> without is a leaf (selects). Each <li> carries
|
|
@@ -54,16 +54,16 @@
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
/* User-facing strings. Indonesian by default
|
|
58
|
-
* path
|
|
57
|
+
/* User-facing strings. Indonesian by default, documented and deliberate for the raw enhancer
|
|
58
|
+
* path, and every one overridable per element, so a host that speaks another language (the
|
|
59
59
|
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
60
60
|
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
61
61
|
var TEXT = {
|
|
62
|
-
back: '
|
|
62
|
+
back: 'Back one level',
|
|
63
63
|
submenu: '{label}, submenu'
|
|
64
64
|
};
|
|
65
65
|
/* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
|
|
66
|
-
as `data-fdy-text-filtertext
|
|
66
|
+
as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
|
|
67
67
|
`data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
|
|
68
68
|
override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
|
|
69
69
|
still resolves, for markup written against 1.39.0. */
|
|
@@ -88,8 +88,8 @@
|
|
|
88
88
|
var root = sourceUl ? parse(sourceUl) : [];
|
|
89
89
|
if (sourceUl) sourceUl.remove();
|
|
90
90
|
|
|
91
|
-
var label = wrap.getAttribute('data-label') || '
|
|
92
|
-
var placeholder = wrap.getAttribute('data-placeholder') || '
|
|
91
|
+
var label = wrap.getAttribute('data-label') || 'Select';
|
|
92
|
+
var placeholder = wrap.getAttribute('data-placeholder') || 'Select…';
|
|
93
93
|
var sep = wrap.getAttribute('data-separator') || ' / ';
|
|
94
94
|
|
|
95
95
|
var trigger = document.createElement('button');
|
package/dist/freeday-cfl.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
/* Freeday
|
|
1
|
+
/* Freeday, choose-from-list enhancer (optional, zero-dependency).
|
|
2
2
|
* Wires a searchable master-data picker dialog to read-only fields.
|
|
3
3
|
* Progressive enhancement over a native <dialog>; in a framework app, drive the
|
|
4
4
|
* dialog as a controlled component (fetchPage callback + server cache), not a store.
|
|
5
5
|
*
|
|
6
6
|
* Markup contract (see docs):
|
|
7
|
-
* - Field group: [data-fdy-cfl="<dialogId>"]
|
|
7
|
+
* - Field group: [data-fdy-cfl="<dialogId>"], usually a `.fdy-input-group` holding a
|
|
8
8
|
* read-only `.fdy-input` and a trigger `<button class="fdy-input-group__btn">`.
|
|
9
9
|
* Optional `data-fdy-cfl-display="<key>"` picks which row dataset key fills the input
|
|
10
10
|
* (default "value"); optional `data-fdy-cfl-summary="{n} dipilih"` formats the multi label.
|
|
11
|
-
* - Standalone opener: [data-fdy-cfl-open="<dialogId>"]
|
|
11
|
+
* - Standalone opener: [data-fdy-cfl-open="<dialogId>"], opens the dialog with no bound field.
|
|
12
12
|
* - Dialog: <dialog id="<dialogId>" class="fdy-modal fdy-modal--cfl"> containing
|
|
13
13
|
* a `[data-fdy-cfl-search]` input, `.fdy-cfl__row` <tr> rows carrying `data-*` fields,
|
|
14
14
|
* a `[data-fdy-cfl-empty]` element, and (multi only) `[data-fdy-cfl-multiple]` on the
|
|
15
15
|
* dialog plus `[data-fdy-cfl-count]` and `[data-fdy-cfl-confirm]` in the footer.
|
|
16
16
|
*
|
|
17
17
|
* Emits a bubbling "fdy-cfl-select" CustomEvent on the field group (or the dialog when
|
|
18
|
-
* opened standalone): detail {row} for single-select, {rows} for multi-select
|
|
18
|
+
* opened standalone): detail {row} for single-select, {rows} for multi-select, each a plain
|
|
19
19
|
* object copy of the row's dataset. Consumers fill sibling fields (code → name) from it.
|
|
20
20
|
*/
|
|
21
21
|
(function () {
|
|
@@ -34,15 +34,15 @@
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
/* User-facing strings. Indonesian by default
|
|
38
|
-
* path
|
|
37
|
+
/* User-facing strings. Indonesian by default, documented and deliberate for the raw enhancer
|
|
38
|
+
* path, and every one overridable per element, so a host that speaks another language (the
|
|
39
39
|
* Blazor adapters, an English app on the raw path) supplies its own without forking this file.
|
|
40
40
|
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
41
41
|
var TEXT = {
|
|
42
|
-
selected: '{n}
|
|
42
|
+
selected: '{n} selected'
|
|
43
43
|
};
|
|
44
44
|
/* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
|
|
45
|
-
as `data-fdy-text-filtertext
|
|
45
|
+
as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
|
|
46
46
|
`data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
|
|
47
47
|
override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
|
|
48
48
|
still resolves, for markup written against 1.39.0. */
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
var input = activeGroup.querySelector('.fdy-input');
|
|
104
104
|
if (input) {
|
|
105
105
|
if (detail.rows) {
|
|
106
|
-
var tpl = activeGroup.getAttribute('data-fdy-cfl-summary') || '{n}
|
|
106
|
+
var tpl = activeGroup.getAttribute('data-fdy-cfl-summary') || '{n} selected';
|
|
107
107
|
input.value = detail.rows.length ? tpl.replace('{n}', String(detail.rows.length)) : '';
|
|
108
108
|
} else {
|
|
109
109
|
var key = activeGroup.getAttribute('data-fdy-cfl-display') || 'value';
|