@cahyo-dimas/freeday 1.17.0 → 1.20.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 +202 -0
- package/COMPONENTS.md +748 -0
- package/README.id.md +20 -2
- package/README.md +32 -8
- package/USAGE.md +127 -0
- package/adapters/blazor/FdyTable.razor.cs +66 -7
- package/adapters/blazor/TableTypes.cs +6 -0
- package/adapters/react/components/FdyTable.tsx +43 -8
- package/adapters/vue/components/FdyDrawer.vue +5 -2
- package/adapters/vue/components/FdyModal.vue +5 -2
- package/adapters/vue/components/FdyTable.vue +42 -6
- package/dist/freeday.bundle.css +120 -9
- package/dist/freeday.css +103 -8
- package/dist/freeday.tokens.css +17 -1
- package/docs/agent-onboarding.md +151 -0
- package/docs/getting-started.md +454 -0
- package/docs/integrations.md +321 -0
- package/docs/reference-screen.html +461 -0
- package/package.json +11 -3
- package/src/components/avatar.css +8 -8
- package/src/components/card.css +3 -0
- package/src/components/chip.css +12 -0
- package/src/components/composition.css +49 -0
- package/src/components/list.css +29 -0
- package/tokens/breakpoints.d.ts +3 -0
- package/tokens/breakpoints.mjs +8 -1
- package/tokens/tokens.json +14 -2
- package/src/components/.gitkeep +0 -0
- package/src/freeday-autocomplete.js +0 -135
- package/src/freeday-breakpoint.js +0 -51
- package/src/freeday-carousel.js +0 -111
- package/src/freeday-cascade.js +0 -256
- package/src/freeday-cfl.js +0 -213
- package/src/freeday-chart.js +0 -429
- package/src/freeday-chip.js +0 -83
- package/src/freeday-datepicker.js +0 -321
- package/src/freeday-datetime.js +0 -83
- package/src/freeday-drawer.js +0 -43
- package/src/freeday-form.js +0 -181
- package/src/freeday-mask.js +0 -114
- package/src/freeday-menu.js +0 -93
- package/src/freeday-popover.js +0 -69
- package/src/freeday-rating.js +0 -50
- package/src/freeday-select.js +0 -218
- package/src/freeday-slider.js +0 -34
- package/src/freeday-stepper.js +0 -90
- package/src/freeday-table.js +0 -475
- package/src/freeday-tabs.js +0 -68
- package/src/freeday-timepicker.js +0 -180
- package/src/freeday-toast.js +0 -105
- package/src/freeday-tree.js +0 -94
- package/src/freeday-upload.js +0 -206
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* Freeday — List: the FLAT row container.
|
|
2
|
+
*
|
|
3
|
+
* USAGE.md §3 puts list rows among the surfaces that should be flat, but until now the only
|
|
4
|
+
* container the kit shipped was .fdy-card — which carries --shadow-lift (a real 34px lift). A
|
|
5
|
+
* responsive table that becomes a list below `md` therefore had to choose between a stack of
|
|
6
|
+
* shadowed cards (against the doctrine) or a hand-built box, which needs a colour and so escapes
|
|
7
|
+
* the token system. This is that missing container: one flat surface, hairline dividers, no shadow.
|
|
8
|
+
*
|
|
9
|
+
* Not to be confused with .fdy-list-reset (base.css), which only strips UA bullets/indent.
|
|
10
|
+
* Works on <ul>/<ol> (list-style is reset here) or on plain <div>s. */
|
|
11
|
+
.fdy-list{list-style:none;margin:0;padding:0;background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;}
|
|
12
|
+
.fdy-list__row{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4) var(--space-5);min-width:0;}
|
|
13
|
+
/* Divider between rows. Two shapes are supported and both are load-bearing: rows as direct children
|
|
14
|
+
* of the list, and rows wrapped in <li> (the semantic shape, where the ADJACENT siblings are the
|
|
15
|
+
* <li>s — a bare `.fdy-list__row + .fdy-list__row` silently matches nothing there). */
|
|
16
|
+
.fdy-list__row + .fdy-list__row,
|
|
17
|
+
.fdy-list > * + * > .fdy-list__row{border-top:var(--bw) solid var(--color-border-muted);}
|
|
18
|
+
/* A row that is itself the control: render it as <button>/<a> and the UA box is reset without
|
|
19
|
+
* touching the list surface (same contract as .fdy-card--button). */
|
|
20
|
+
.fdy-list__row--button{width:100%;text-align:inherit;color:inherit;font:inherit;background:none;border:0;appearance:none;-webkit-appearance:none;cursor:pointer;}
|
|
21
|
+
.fdy-list__row--button:hover,.fdy-list__row--interactive:hover{background:var(--color-surface-2);}
|
|
22
|
+
.fdy-list__row--button:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--color-primary);}
|
|
23
|
+
.fdy-list__row--interactive{cursor:pointer;}
|
|
24
|
+
/* Row internals: a title/meta stack that truncates, and a trailing slot pinned right. */
|
|
25
|
+
.fdy-list__main{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;flex:1;}
|
|
26
|
+
.fdy-list__title{font-weight:var(--weight-medium);color:var(--color-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
27
|
+
.fdy-list__meta{font-size:var(--text-sm);color:var(--color-text-muted);display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;}
|
|
28
|
+
.fdy-list__aside{flex:none;display:flex;align-items:center;gap:var(--space-2);}
|
|
29
|
+
/* Density: rows tighten with the rest of the kit (--space-* mid-range steps under compact). */
|
package/tokens/breakpoints.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// Types for tokens/breakpoints.mjs — the JS-side breakpoint scale in px
|
|
2
2
|
// (mirrors the sm/md/lg/xl utilities in src/components/breakpoints.css).
|
|
3
3
|
export declare const breakpoints: {
|
|
4
|
+
/** Width at which .fdy-app's sidebar turns from off-canvas drawer into a static column.
|
|
5
|
+
* Not part of the sm/md/lg/xl ramp — it mirrors app-shell.css and nothing else. */
|
|
6
|
+
nav: number;
|
|
4
7
|
sm: number;
|
|
5
8
|
md: number;
|
|
6
9
|
lg: number;
|
package/tokens/breakpoints.mjs
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
// Freeday breakpoint scale in px — mirrors src/components/breakpoints.css utilities
|
|
2
2
|
// (sm/md/lg/xl). CSS @media can't read custom properties, so this is the JS-side
|
|
3
3
|
// source of the scale for matchMedia / app @media alignment.
|
|
4
|
-
|
|
4
|
+
//
|
|
5
|
+
// `nav` is not part of the sm/md/lg/xl ramp: it is the width at which .fdy-app switches the
|
|
6
|
+
// sidebar from an off-canvas drawer (below) to a static column (at and above), hard-wired in
|
|
7
|
+
// src/components/app-shell.css. Anything that has to agree with the shell — a matchMedia guard
|
|
8
|
+
// deciding whether the nav toggle sets --nav-open or --nav-collapsed, a utility-framework
|
|
9
|
+
// variant — must use THIS number, not md. Aligning such code to md (960) leaves 721–959px
|
|
10
|
+
// broken: the sidebar is already static while the script still thinks it is an overlay.
|
|
11
|
+
export const breakpoints = { nav: 721, sm: 600, md: 960, lg: 1280, xl: 1920 };
|
package/tokens/tokens.json
CHANGED
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"weight": { "regular": { "$value": "400" }, "medium": { "$value": "500" }, "semibold": { "$value": "600" }, "bold": { "$value": "700" } },
|
|
43
43
|
|
|
44
44
|
"space": {
|
|
45
|
-
"0": { "$value": "0" }, "1": { "$value": "0.25rem" }, "2": { "$value": "0.5rem" }, "3": { "$value": "0.75rem" },
|
|
46
|
-
"4": { "$value": "1rem" }, "5": { "$value": "1.25rem" }, "6": { "$value": "1.5rem" }, "8": { "$value": "2rem" },
|
|
45
|
+
"0": { "$value": "0" }, "1": { "$value": "0.25rem" }, "2": { "$value": "0.5rem" }, "3": { "$value": "0.75rem", "$compact": "0.625rem" },
|
|
46
|
+
"4": { "$value": "1rem", "$compact": "0.75rem" }, "5": { "$value": "1.25rem", "$compact": "1rem" }, "6": { "$value": "1.5rem", "$compact": "1.25rem" }, "8": { "$value": "2rem" },
|
|
47
47
|
"10": { "$value": "2.5rem" }, "12": { "$value": "3rem" }, "16": { "$value": "4rem" }, "20": { "$value": "5rem" }, "24": { "$value": "6rem" }
|
|
48
48
|
},
|
|
49
49
|
"radius": { "xs": { "$value": "3px" }, "sm": { "$value": "4px" }, "md": { "$value": "6px" }, "lg": { "$value": "10px" }, "xl": { "$value": "14px" }, "full": { "$value": "999px" } },
|
|
@@ -118,6 +118,18 @@
|
|
|
118
118
|
"tick": { "$value": "{slate.400}", "$dark": "{slate.600}" }
|
|
119
119
|
},
|
|
120
120
|
|
|
121
|
+
"_comment_tone": "General categorical palette — the same 8 validated hues as chart, aliased under a chart-neutral name for avatars, chips, tags, legend swatches (theme-follows via chart's own $dark).",
|
|
122
|
+
"tone": {
|
|
123
|
+
"1": { "$value": "{chart.1}" },
|
|
124
|
+
"2": { "$value": "{chart.2}" },
|
|
125
|
+
"3": { "$value": "{chart.3}" },
|
|
126
|
+
"4": { "$value": "{chart.4}" },
|
|
127
|
+
"5": { "$value": "{chart.5}" },
|
|
128
|
+
"6": { "$value": "{chart.6}" },
|
|
129
|
+
"7": { "$value": "{chart.7}" },
|
|
130
|
+
"8": { "$value": "{chart.8}" }
|
|
131
|
+
},
|
|
132
|
+
|
|
121
133
|
"focus-ring": { "$value": "{azure.600}", "$dark": "{azure.400}" },
|
|
122
134
|
"focus-ring-width": { "$value": "2px" }
|
|
123
135
|
}
|
package/src/components/.gitkeep
DELETED
|
File without changes
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
/* Freeday — autocomplete enhancer (optional, zero-dependency).
|
|
2
|
-
* Editable combobox (WAI-ARIA APG): a text input filters a role="listbox" of role="option"
|
|
3
|
-
* items as you type; select fills the input. Auto-inits [data-fdy-autocomplete]. Options are
|
|
4
|
-
* static markup filtered client-side — in an app, fetch/filter server-side and re-render options.
|
|
5
|
-
*
|
|
6
|
-
* Markup: <div data-fdy-autocomplete class="fdy-autocomplete">
|
|
7
|
-
* <input class="fdy-input" role="combobox" aria-expanded="false" aria-autocomplete="list"
|
|
8
|
-
* aria-controls="listId" autocomplete="off">
|
|
9
|
-
* <ul class="fdy-autocomplete__listbox" id="listId" role="listbox" hidden>
|
|
10
|
-
* <li class="fdy-autocomplete__option" role="option">…</li>…
|
|
11
|
-
* <li class="fdy-autocomplete__empty" hidden>Tak ada hasil</li></ul></div>
|
|
12
|
-
* Emits a bubbling "fdy-autocomplete-select" CustomEvent (detail {value, option}).
|
|
13
|
-
*/
|
|
14
|
-
(function () {
|
|
15
|
-
'use strict';
|
|
16
|
-
|
|
17
|
-
var seq = 0;
|
|
18
|
-
|
|
19
|
-
function initAutocomplete(root) {
|
|
20
|
-
if (root.dataset.fdyAcReady === '1') return;
|
|
21
|
-
root.dataset.fdyAcReady = '1';
|
|
22
|
-
|
|
23
|
-
var input = root.querySelector('input[role="combobox"]') || root.querySelector('input');
|
|
24
|
-
var listbox = root.querySelector('[role="listbox"]');
|
|
25
|
-
if (!input || !listbox) return;
|
|
26
|
-
var options = Array.prototype.slice.call(listbox.querySelectorAll('[role="option"]'));
|
|
27
|
-
var emptyEl = listbox.querySelector('.fdy-autocomplete__empty');
|
|
28
|
-
var active = -1;
|
|
29
|
-
|
|
30
|
-
options.forEach(function (o) {
|
|
31
|
-
if (!o.id) { seq += 1; o.id = 'fdy-ac-opt-' + seq; }
|
|
32
|
-
o.dataset.label = o.textContent.trim();
|
|
33
|
-
});
|
|
34
|
-
listbox.hidden = true;
|
|
35
|
-
input.setAttribute('aria-expanded', 'false');
|
|
36
|
-
|
|
37
|
-
function visible() { return options.filter(function (o) { return !o.hidden; }); }
|
|
38
|
-
function clearHighlight() { options.forEach(function (o) { o.classList.remove('is-highlighted'); }); }
|
|
39
|
-
|
|
40
|
-
var _pop = null;
|
|
41
|
-
function popCtl() { if (_pop === null && window.FreedayPopover) _pop = window.FreedayPopover.attach(listbox, input); return _pop; }
|
|
42
|
-
function open() {
|
|
43
|
-
if (!listbox.hidden) return;
|
|
44
|
-
var p = popCtl(); if (p) p.show(); else listbox.hidden = false;
|
|
45
|
-
input.setAttribute('aria-expanded', 'true');
|
|
46
|
-
document.addEventListener('click', onDocClick, true);
|
|
47
|
-
}
|
|
48
|
-
function close() {
|
|
49
|
-
if (listbox.hidden) return;
|
|
50
|
-
var p = popCtl(); if (p) p.hide(); else listbox.hidden = true;
|
|
51
|
-
input.setAttribute('aria-expanded', 'false');
|
|
52
|
-
input.removeAttribute('aria-activedescendant');
|
|
53
|
-
active = -1;
|
|
54
|
-
clearHighlight();
|
|
55
|
-
document.removeEventListener('click', onDocClick, true);
|
|
56
|
-
}
|
|
57
|
-
function onDocClick(e) { if (!root.contains(e.target)) close(); }
|
|
58
|
-
|
|
59
|
-
function highlight(i) {
|
|
60
|
-
var vis = visible();
|
|
61
|
-
clearHighlight();
|
|
62
|
-
if (!vis.length || i < 0 || i >= vis.length) { active = -1; input.removeAttribute('aria-activedescendant'); return; }
|
|
63
|
-
active = i;
|
|
64
|
-
vis[i].classList.add('is-highlighted');
|
|
65
|
-
input.setAttribute('aria-activedescendant', vis[i].id);
|
|
66
|
-
vis[i].scrollIntoView({ block: 'nearest' });
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function applyFilter() {
|
|
70
|
-
var q = input.value.trim().toLowerCase();
|
|
71
|
-
var shown = 0;
|
|
72
|
-
options.forEach(function (o) {
|
|
73
|
-
var match = !q || o.dataset.label.toLowerCase().indexOf(q) !== -1;
|
|
74
|
-
o.hidden = !match;
|
|
75
|
-
if (match) shown++;
|
|
76
|
-
});
|
|
77
|
-
if (emptyEl) emptyEl.hidden = shown !== 0;
|
|
78
|
-
active = -1;
|
|
79
|
-
input.removeAttribute('aria-activedescendant');
|
|
80
|
-
clearHighlight();
|
|
81
|
-
open();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function choose(opt) {
|
|
85
|
-
input.value = opt.dataset.label;
|
|
86
|
-
options.forEach(function (o) { o.setAttribute('aria-selected', o === opt ? 'true' : 'false'); });
|
|
87
|
-
close();
|
|
88
|
-
root.dispatchEvent(new CustomEvent('fdy-autocomplete-select', { bubbles: true, detail: { value: opt.dataset.label, option: opt } }));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
input.addEventListener('input', applyFilter);
|
|
92
|
-
input.addEventListener('focus', function () { applyFilter(); });
|
|
93
|
-
input.addEventListener('keydown', function (e) {
|
|
94
|
-
var vis = visible();
|
|
95
|
-
switch (e.key) {
|
|
96
|
-
case 'ArrowDown':
|
|
97
|
-
e.preventDefault();
|
|
98
|
-
if (listbox.hidden) open();
|
|
99
|
-
highlight(active + 1 < vis.length ? active + 1 : 0);
|
|
100
|
-
break;
|
|
101
|
-
case 'ArrowUp':
|
|
102
|
-
e.preventDefault();
|
|
103
|
-
if (listbox.hidden) open();
|
|
104
|
-
highlight(active - 1 >= 0 ? active - 1 : vis.length - 1);
|
|
105
|
-
break;
|
|
106
|
-
case 'Enter':
|
|
107
|
-
if (!listbox.hidden && active >= 0) { e.preventDefault(); choose(vis[active]); }
|
|
108
|
-
break;
|
|
109
|
-
case 'Escape':
|
|
110
|
-
if (!listbox.hidden) { e.preventDefault(); close(); }
|
|
111
|
-
break;
|
|
112
|
-
case 'Tab':
|
|
113
|
-
close();
|
|
114
|
-
break;
|
|
115
|
-
default: break;
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
listbox.addEventListener('click', function (e) {
|
|
119
|
-
var opt = e.target.closest('[role="option"]');
|
|
120
|
-
if (opt && !opt.hidden) choose(opt);
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function initAll(context) {
|
|
125
|
-
Array.prototype.forEach.call((context || document).querySelectorAll('[data-fdy-autocomplete]'), initAutocomplete);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (document.readyState === 'loading') {
|
|
129
|
-
document.addEventListener('DOMContentLoaded', function () { initAll(); });
|
|
130
|
-
} else {
|
|
131
|
-
initAll();
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
window.FreedayAutocomplete = { init: initAutocomplete, initAll: initAll };
|
|
135
|
-
})();
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/* Freeday — breakpoint provider (optional, zero-dependency).
|
|
2
|
-
* The MudBreakpointProvider equivalent for JS consumers: stamps the current breakpoint on
|
|
3
|
-
* <html data-breakpoint="xs|sm|md|lg|xl"> and emits a bubbling "fdy-breakpoint-change"
|
|
4
|
-
* CustomEvent (detail {breakpoint}) whenever it changes. Also fills any element with
|
|
5
|
-
* [data-fdy-breakpoint-label] with the current name. Read it live via FreedayBreakpoint.get().
|
|
6
|
-
* Scale (min-width, px): sm 600 · md 960 · lg 1280 · xl 1920 (matches breakpoints.css).
|
|
7
|
-
*/
|
|
8
|
-
(function () {
|
|
9
|
-
'use strict';
|
|
10
|
-
|
|
11
|
-
var STEPS = [['xl', 1920], ['lg', 1280], ['md', 960], ['sm', 600], ['xs', 0]];
|
|
12
|
-
|
|
13
|
-
function current() {
|
|
14
|
-
var w = window.innerWidth;
|
|
15
|
-
for (var i = 0; i < STEPS.length; i++) {
|
|
16
|
-
if (w >= STEPS[i][1]) return STEPS[i][0];
|
|
17
|
-
}
|
|
18
|
-
return 'xs';
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
var root = document.documentElement;
|
|
22
|
-
var last = null;
|
|
23
|
-
|
|
24
|
-
function update() {
|
|
25
|
-
var bp = current();
|
|
26
|
-
if (bp === last) return;
|
|
27
|
-
last = bp;
|
|
28
|
-
root.setAttribute('data-breakpoint', bp);
|
|
29
|
-
Array.prototype.forEach.call(document.querySelectorAll('[data-fdy-breakpoint-label]'), function (el) {
|
|
30
|
-
el.textContent = bp;
|
|
31
|
-
});
|
|
32
|
-
root.dispatchEvent(new CustomEvent('fdy-breakpoint-change', { bubbles: true, detail: { breakpoint: bp } }));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
window.addEventListener('resize', update, { passive: true });
|
|
36
|
-
if (document.readyState === 'loading') {
|
|
37
|
-
document.addEventListener('DOMContentLoaded', update);
|
|
38
|
-
} else {
|
|
39
|
-
update();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
window.FreedayBreakpoint = {
|
|
43
|
-
get: function () { return last || current(); },
|
|
44
|
-
current: current,
|
|
45
|
-
// true if the current breakpoint is `name` or wider (e.g. is('md') → md, lg, or xl)
|
|
46
|
-
isUp: function (name) {
|
|
47
|
-
var order = ['xs', 'sm', 'md', 'lg', 'xl'];
|
|
48
|
-
return order.indexOf(this.get()) >= order.indexOf(name);
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
})();
|
package/src/freeday-carousel.js
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/* Freeday — carousel enhancer (optional, zero-dependency).
|
|
2
|
-
* Scroll-snap slider with prev/next arrows, generated dot indicators, keyboard (←/→),
|
|
3
|
-
* and optional autoplay (data-fdy-autoplay="4000", pauses on hover/focus). Auto-inits
|
|
4
|
-
* [data-fdy-carousel].
|
|
5
|
-
*
|
|
6
|
-
* Markup: <div class="fdy-carousel" data-fdy-carousel role="region"
|
|
7
|
-
* aria-roledescription="carousel" aria-label="…" tabindex="0">
|
|
8
|
-
* <button class="fdy-carousel__arrow fdy-carousel__arrow--prev" aria-label="Sebelumnya">…</button>
|
|
9
|
-
* <div class="fdy-carousel__viewport">
|
|
10
|
-
* <div class="fdy-carousel__slide" role="group" aria-roledescription="slide">…</div>…
|
|
11
|
-
* </div>
|
|
12
|
-
* <button class="fdy-carousel__arrow fdy-carousel__arrow--next" aria-label="Berikutnya">…</button>
|
|
13
|
-
* <div class="fdy-carousel__dots"></div> <!-- dots generated here -->
|
|
14
|
-
* </div>
|
|
15
|
-
* Emits a bubbling "fdy-carousel-change" CustomEvent (detail {index}).
|
|
16
|
-
*/
|
|
17
|
-
(function () {
|
|
18
|
-
'use strict';
|
|
19
|
-
|
|
20
|
-
function initCarousel(root) {
|
|
21
|
-
if (root.dataset.fdyCarouselReady === '1') return;
|
|
22
|
-
root.dataset.fdyCarouselReady = '1';
|
|
23
|
-
|
|
24
|
-
var viewport = root.querySelector('.fdy-carousel__viewport');
|
|
25
|
-
var slides = Array.prototype.slice.call(root.querySelectorAll('.fdy-carousel__slide'));
|
|
26
|
-
if (!viewport || !slides.length) return;
|
|
27
|
-
var prev = root.querySelector('.fdy-carousel__arrow--prev');
|
|
28
|
-
var next = root.querySelector('.fdy-carousel__arrow--next');
|
|
29
|
-
var dotsWrap = root.querySelector('.fdy-carousel__dots');
|
|
30
|
-
var index = 0;
|
|
31
|
-
|
|
32
|
-
slides.forEach(function (s, i) {
|
|
33
|
-
if (!s.hasAttribute('aria-label')) s.setAttribute('aria-label', (i + 1) + ' dari ' + slides.length);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
var dots = [];
|
|
37
|
-
if (dotsWrap) {
|
|
38
|
-
slides.forEach(function (s, i) {
|
|
39
|
-
var b = document.createElement('button');
|
|
40
|
-
b.type = 'button';
|
|
41
|
-
b.className = 'fdy-carousel__dot';
|
|
42
|
-
b.setAttribute('aria-label', 'Slide ' + (i + 1));
|
|
43
|
-
b.addEventListener('click', function () { goTo(i); });
|
|
44
|
-
dotsWrap.appendChild(b);
|
|
45
|
-
dots.push(b);
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function clamp(i) { return Math.max(0, Math.min(slides.length - 1, i)); }
|
|
50
|
-
|
|
51
|
-
function sync() {
|
|
52
|
-
dots.forEach(function (d, i) { d.setAttribute('aria-current', i === index ? 'true' : 'false'); });
|
|
53
|
-
if (prev) prev.disabled = index === 0;
|
|
54
|
-
if (next) next.disabled = index === slides.length - 1;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function goTo(i) {
|
|
58
|
-
index = clamp(i);
|
|
59
|
-
viewport.scrollTo({ left: index * viewport.clientWidth, behavior: 'smooth' });
|
|
60
|
-
sync();
|
|
61
|
-
root.dispatchEvent(new CustomEvent('fdy-carousel-change', { bubbles: true, detail: { index: index } }));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (prev) prev.addEventListener('click', function () { goTo(index - 1); });
|
|
65
|
-
if (next) next.addEventListener('click', function () { goTo(index + 1); });
|
|
66
|
-
root.addEventListener('keydown', function (e) {
|
|
67
|
-
if (e.key === 'ArrowLeft') { e.preventDefault(); goTo(index - 1); }
|
|
68
|
-
else if (e.key === 'ArrowRight') { e.preventDefault(); goTo(index + 1); }
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// Keep index in sync when the user swipes/scrolls the viewport directly.
|
|
72
|
-
var ticking = false;
|
|
73
|
-
viewport.addEventListener('scroll', function () {
|
|
74
|
-
if (ticking) return;
|
|
75
|
-
ticking = true;
|
|
76
|
-
requestAnimationFrame(function () {
|
|
77
|
-
ticking = false;
|
|
78
|
-
var w = viewport.clientWidth || 1;
|
|
79
|
-
var i = clamp(Math.round(viewport.scrollLeft / w));
|
|
80
|
-
if (i !== index) { index = i; sync(); }
|
|
81
|
-
});
|
|
82
|
-
}, { passive: true });
|
|
83
|
-
|
|
84
|
-
var autoplay = parseInt(root.getAttribute('data-fdy-autoplay'), 10);
|
|
85
|
-
if (autoplay > 0) {
|
|
86
|
-
var timer = null;
|
|
87
|
-
var start = function () { if (!timer) timer = setInterval(function () { goTo(index >= slides.length - 1 ? 0 : index + 1); }, autoplay); };
|
|
88
|
-
var stop = function () { if (timer) { clearInterval(timer); timer = null; } };
|
|
89
|
-
root.addEventListener('mouseenter', stop);
|
|
90
|
-
root.addEventListener('mouseleave', start);
|
|
91
|
-
root.addEventListener('focusin', stop);
|
|
92
|
-
root.addEventListener('focusout', start);
|
|
93
|
-
var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
94
|
-
if (!reduce) start();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
sync();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function initAll(context) {
|
|
101
|
-
Array.prototype.forEach.call((context || document).querySelectorAll('[data-fdy-carousel]'), initCarousel);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (document.readyState === 'loading') {
|
|
105
|
-
document.addEventListener('DOMContentLoaded', function () { initAll(); });
|
|
106
|
-
} else {
|
|
107
|
-
initAll();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
window.FreedayCarousel = { init: initCarousel, initAll: initAll };
|
|
111
|
-
})();
|
package/src/freeday-cascade.js
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
/* Freeday — cascade select enhancer (optional, zero-dependency).
|
|
2
|
-
* Turns a [data-fdy-cascade] wrapper into a hierarchical drill-down picker. The
|
|
3
|
-
* data model is a hidden nested <ul> inside the wrapper: a <li> with a child <ul>
|
|
4
|
-
* is a branch (drills in), a <li> without is a leaf (selects). Each <li> carries
|
|
5
|
-
* its value in data-value; its own text (before any nested <ul>) is the label.
|
|
6
|
-
*
|
|
7
|
-
* The popover shows one level at a time (a listbox); a branch drills in, the back
|
|
8
|
-
* control ascends, a leaf selects and stores the full path as the value.
|
|
9
|
-
* Attributes: data-label, data-placeholder, data-value (pre-select a leaf value),
|
|
10
|
-
* data-separator (path separator in the display, default " / ").
|
|
11
|
-
*
|
|
12
|
-
* Keyboard: trigger opens on click / Enter / Space / ArrowDown. In the list:
|
|
13
|
-
* Up/Down move, Home/End jump, ArrowRight/Enter/Space drill into a branch or
|
|
14
|
-
* select a leaf, ArrowLeft/Backspace go up a level, Esc closes (focus returns to
|
|
15
|
-
* the trigger). Emits a bubbling "fdy-cascade-change" (detail {value, path, labels}).
|
|
16
|
-
*/
|
|
17
|
-
(function () {
|
|
18
|
-
'use strict';
|
|
19
|
-
|
|
20
|
-
var seq = 0;
|
|
21
|
-
var BACK = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"></path></svg>';
|
|
22
|
-
|
|
23
|
-
// Own text of an <li>, excluding any nested <ul>.
|
|
24
|
-
function ownLabel(li) {
|
|
25
|
-
var s = '';
|
|
26
|
-
Array.prototype.forEach.call(li.childNodes, function (n) {
|
|
27
|
-
if (n.nodeType === 3) s += n.textContent;
|
|
28
|
-
else if (n.nodeType === 1 && n.tagName !== 'UL') s += n.textContent;
|
|
29
|
-
});
|
|
30
|
-
return s.trim();
|
|
31
|
-
}
|
|
32
|
-
function parse(ul) {
|
|
33
|
-
var nodes = [];
|
|
34
|
-
Array.prototype.forEach.call(ul.children, function (li) {
|
|
35
|
-
if (li.tagName !== 'LI') return;
|
|
36
|
-
var childUl = li.querySelector(':scope > ul');
|
|
37
|
-
nodes.push({
|
|
38
|
-
label: ownLabel(li),
|
|
39
|
-
value: li.getAttribute('data-value') || ownLabel(li),
|
|
40
|
-
children: childUl ? parse(childUl) : null
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
return nodes;
|
|
44
|
-
}
|
|
45
|
-
// Depth-first search for the stack of labels leading to a leaf value.
|
|
46
|
-
function pathTo(nodes, value, trail) {
|
|
47
|
-
for (var i = 0; i < nodes.length; i++) {
|
|
48
|
-
var n = nodes[i];
|
|
49
|
-
var here = trail.concat([n]);
|
|
50
|
-
if (!n.children && n.value === value) return here;
|
|
51
|
-
if (n.children) { var found = pathTo(n.children, value, here); if (found) return found; }
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function initCascade(wrap) {
|
|
57
|
-
if (wrap.dataset.fdyCascadeReady === '1') return;
|
|
58
|
-
wrap.dataset.fdyCascadeReady = '1';
|
|
59
|
-
wrap.classList.add('fdy-cascade');
|
|
60
|
-
|
|
61
|
-
var sourceUl = wrap.querySelector('ul');
|
|
62
|
-
var root = sourceUl ? parse(sourceUl) : [];
|
|
63
|
-
if (sourceUl) sourceUl.remove();
|
|
64
|
-
|
|
65
|
-
var label = wrap.getAttribute('data-label') || 'Pilih';
|
|
66
|
-
var placeholder = wrap.getAttribute('data-placeholder') || 'Pilih…';
|
|
67
|
-
var sep = wrap.getAttribute('data-separator') || ' / ';
|
|
68
|
-
|
|
69
|
-
var trigger = document.createElement('button');
|
|
70
|
-
trigger.type = 'button';
|
|
71
|
-
trigger.className = 'fdy-cascade__trigger';
|
|
72
|
-
trigger.setAttribute('aria-haspopup', 'true');
|
|
73
|
-
trigger.setAttribute('aria-expanded', 'false');
|
|
74
|
-
trigger.setAttribute('aria-label', label);
|
|
75
|
-
var valueSpan = document.createElement('span');
|
|
76
|
-
valueSpan.className = 'fdy-cascade__value';
|
|
77
|
-
trigger.appendChild(valueSpan);
|
|
78
|
-
|
|
79
|
-
var panel = document.createElement('div');
|
|
80
|
-
panel.className = 'fdy-cascade__panel';
|
|
81
|
-
panel.hidden = true;
|
|
82
|
-
var head = document.createElement('div');
|
|
83
|
-
head.className = 'fdy-cascade__head';
|
|
84
|
-
var back = document.createElement('button');
|
|
85
|
-
back.type = 'button';
|
|
86
|
-
back.className = 'fdy-cascade__back';
|
|
87
|
-
back.setAttribute('aria-label', 'Kembali satu tingkat');
|
|
88
|
-
back.innerHTML = BACK;
|
|
89
|
-
back.hidden = true;
|
|
90
|
-
var crumb = document.createElement('span');
|
|
91
|
-
crumb.className = 'fdy-cascade__crumb';
|
|
92
|
-
crumb.setAttribute('aria-live', 'polite');
|
|
93
|
-
head.appendChild(back);
|
|
94
|
-
head.appendChild(crumb);
|
|
95
|
-
var list = document.createElement('ul');
|
|
96
|
-
seq += 1;
|
|
97
|
-
list.className = 'fdy-cascade__list';
|
|
98
|
-
list.id = 'fdy-cascade-list-' + seq;
|
|
99
|
-
list.setAttribute('role', 'listbox');
|
|
100
|
-
list.setAttribute('aria-label', label);
|
|
101
|
-
list.tabIndex = -1;
|
|
102
|
-
panel.appendChild(head);
|
|
103
|
-
panel.appendChild(list);
|
|
104
|
-
|
|
105
|
-
wrap.appendChild(trigger);
|
|
106
|
-
wrap.appendChild(panel);
|
|
107
|
-
|
|
108
|
-
var stack = []; // branch nodes drilled through (context)
|
|
109
|
-
var current = root; // nodes at the visible level
|
|
110
|
-
var opts = []; // rendered <li> elements
|
|
111
|
-
var active = -1;
|
|
112
|
-
var selectedValue = wrap.getAttribute('data-value') || '';
|
|
113
|
-
|
|
114
|
-
function render() {
|
|
115
|
-
list.innerHTML = '';
|
|
116
|
-
opts = [];
|
|
117
|
-
back.hidden = stack.length === 0;
|
|
118
|
-
crumb.textContent = stack.length ? stack.map(function (n) { return n.label; }).join(sep) : label;
|
|
119
|
-
current.forEach(function (node, i) {
|
|
120
|
-
var li = document.createElement('li');
|
|
121
|
-
seq += 1; li.id = 'fdy-cascade-opt-' + seq;
|
|
122
|
-
li.className = 'fdy-cascade__opt';
|
|
123
|
-
li.setAttribute('role', 'option');
|
|
124
|
-
li.setAttribute('data-index', String(i));
|
|
125
|
-
var isBranch = !!node.children;
|
|
126
|
-
li.setAttribute('aria-selected', (!isBranch && node.value === selectedValue) ? 'true' : 'false');
|
|
127
|
-
if (isBranch) li.setAttribute('aria-label', node.label + ', submenu');
|
|
128
|
-
var lbl = document.createElement('span');
|
|
129
|
-
lbl.className = 'fdy-cascade__opt-label';
|
|
130
|
-
lbl.textContent = node.label;
|
|
131
|
-
li.appendChild(lbl);
|
|
132
|
-
if (isBranch) {
|
|
133
|
-
var arrow = document.createElement('span');
|
|
134
|
-
arrow.className = 'fdy-cascade__opt-arrow';
|
|
135
|
-
arrow.setAttribute('aria-hidden', 'true');
|
|
136
|
-
li.appendChild(arrow);
|
|
137
|
-
}
|
|
138
|
-
list.appendChild(li);
|
|
139
|
-
opts.push(li);
|
|
140
|
-
});
|
|
141
|
-
setActive(0);
|
|
142
|
-
}
|
|
143
|
-
function setActive(i) {
|
|
144
|
-
if (active >= 0 && opts[active]) opts[active].classList.remove('is-active');
|
|
145
|
-
active = Math.max(-1, Math.min(opts.length - 1, i));
|
|
146
|
-
if (active >= 0) {
|
|
147
|
-
opts[active].classList.add('is-active');
|
|
148
|
-
list.setAttribute('aria-activedescendant', opts[active].id);
|
|
149
|
-
opts[active].scrollIntoView({ block: 'nearest' });
|
|
150
|
-
} else {
|
|
151
|
-
list.removeAttribute('aria-activedescendant');
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
function drill(i) {
|
|
155
|
-
var node = current[i];
|
|
156
|
-
if (!node || !node.children) return;
|
|
157
|
-
stack.push(node);
|
|
158
|
-
current = node.children;
|
|
159
|
-
render();
|
|
160
|
-
}
|
|
161
|
-
function ascend() {
|
|
162
|
-
if (!stack.length) return;
|
|
163
|
-
stack.pop();
|
|
164
|
-
current = stack.length ? stack[stack.length - 1].children : root;
|
|
165
|
-
render();
|
|
166
|
-
}
|
|
167
|
-
function activate(i) {
|
|
168
|
-
var node = current[i];
|
|
169
|
-
if (!node) return;
|
|
170
|
-
if (node.children) { drill(i); return; }
|
|
171
|
-
selectedValue = node.value;
|
|
172
|
-
var labels = stack.map(function (n) { return n.label; }).concat([node.label]);
|
|
173
|
-
valueSpan.textContent = labels.join(sep);
|
|
174
|
-
valueSpan.classList.remove('fdy-cascade__value--placeholder');
|
|
175
|
-
wrap.dispatchEvent(new CustomEvent('fdy-cascade-change', { bubbles: true, detail: { value: node.value, path: labels.join(sep), labels: labels } }));
|
|
176
|
-
close(true);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
var _pop = null;
|
|
180
|
-
function popCtl() { if (_pop === null && window.FreedayPopover) _pop = window.FreedayPopover.attach(panel, trigger); return _pop; }
|
|
181
|
-
function open() {
|
|
182
|
-
if (!panel.hidden) return;
|
|
183
|
-
// Re-open at the selected leaf's level for quick re-selection.
|
|
184
|
-
var trail = selectedValue ? pathTo(root, selectedValue, []) : null;
|
|
185
|
-
if (trail && trail.length > 1) { stack = trail.slice(0, -1); current = stack[stack.length - 1].children; }
|
|
186
|
-
else { stack = []; current = root; }
|
|
187
|
-
var p = popCtl(); if (p) p.show(); else panel.hidden = false;
|
|
188
|
-
trigger.setAttribute('aria-expanded', 'true');
|
|
189
|
-
trigger.classList.add('is-open');
|
|
190
|
-
render();
|
|
191
|
-
list.focus();
|
|
192
|
-
document.addEventListener('click', onDoc, true);
|
|
193
|
-
}
|
|
194
|
-
function close(returnFocus) {
|
|
195
|
-
if (panel.hidden) return;
|
|
196
|
-
var p = popCtl(); if (p) p.hide(); else panel.hidden = true;
|
|
197
|
-
trigger.setAttribute('aria-expanded', 'false');
|
|
198
|
-
trigger.classList.remove('is-open');
|
|
199
|
-
document.removeEventListener('click', onDoc, true);
|
|
200
|
-
if (returnFocus === true) trigger.focus();
|
|
201
|
-
}
|
|
202
|
-
function onDoc(e) { if (!wrap.contains(e.target)) close(false); }
|
|
203
|
-
|
|
204
|
-
trigger.addEventListener('click', function () { panel.hidden ? open() : close(true); });
|
|
205
|
-
trigger.addEventListener('keydown', function (e) {
|
|
206
|
-
if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') { e.preventDefault(); open(); }
|
|
207
|
-
});
|
|
208
|
-
back.addEventListener('click', function () { ascend(); list.focus(); });
|
|
209
|
-
list.addEventListener('keydown', function (e) {
|
|
210
|
-
switch (e.key) {
|
|
211
|
-
case 'ArrowDown': e.preventDefault(); setActive(active + 1); break;
|
|
212
|
-
case 'ArrowUp': e.preventDefault(); setActive(active - 1); break;
|
|
213
|
-
case 'Home': e.preventDefault(); setActive(0); break;
|
|
214
|
-
case 'End': e.preventDefault(); setActive(opts.length - 1); break;
|
|
215
|
-
case 'ArrowRight': case 'Enter': case ' ': e.preventDefault(); if (active >= 0) activate(active); break;
|
|
216
|
-
case 'ArrowLeft': case 'Backspace': e.preventDefault(); ascend(); break;
|
|
217
|
-
case 'Escape': e.preventDefault(); close(true); break;
|
|
218
|
-
case 'Tab': close(false); break;
|
|
219
|
-
default: break;
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
list.addEventListener('click', function (e) {
|
|
223
|
-
var li = e.target.closest('[role="option"]');
|
|
224
|
-
if (li) activate(opts.indexOf(li));
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
// Initial display: pre-selected leaf shows its full path, else placeholder.
|
|
228
|
-
var initTrail = selectedValue ? pathTo(root, selectedValue, []) : null;
|
|
229
|
-
if (initTrail) {
|
|
230
|
-
valueSpan.textContent = initTrail.map(function (n) { return n.label; }).join(sep);
|
|
231
|
-
} else {
|
|
232
|
-
valueSpan.textContent = placeholder;
|
|
233
|
-
valueSpan.classList.add('fdy-cascade__value--placeholder');
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
var api = {
|
|
237
|
-
wrap: wrap,
|
|
238
|
-
getValue: function () { return selectedValue; },
|
|
239
|
-
clear: function () { selectedValue = ''; valueSpan.textContent = placeholder; valueSpan.classList.add('fdy-cascade__value--placeholder'); }
|
|
240
|
-
};
|
|
241
|
-
wrap._fdyCascade = api;
|
|
242
|
-
return api;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function initAll(context) {
|
|
246
|
-
Array.prototype.forEach.call((context || document).querySelectorAll('[data-fdy-cascade]'), initCascade);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (document.readyState === 'loading') {
|
|
250
|
-
document.addEventListener('DOMContentLoaded', function () { initAll(); });
|
|
251
|
-
} else {
|
|
252
|
-
initAll();
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
window.FreedayCascade = { init: initCascade, initAll: initAll };
|
|
256
|
-
})();
|