@cahyo-dimas/freeday 1.7.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 +447 -0
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/adapters/blazor/freeday-blazor.js +79 -0
- package/adapters/react/components/FdyAutocomplete.tsx +159 -0
- package/adapters/react/components/FdyCascade.tsx +230 -0
- package/adapters/react/components/FdyCfl.tsx +362 -0
- package/adapters/react/components/FdyChart.tsx +69 -0
- package/adapters/react/components/FdyCombo.tsx +166 -0
- package/adapters/react/components/FdyDateRange.tsx +66 -0
- package/adapters/react/components/FdyDatepicker.tsx +354 -0
- package/adapters/react/index.d.ts +37 -0
- package/adapters/react/index.js +9 -0
- package/adapters/react/tsconfig.json +14 -0
- package/adapters/react/useFreeday.js +44 -0
- package/adapters/react/usePopover.ts +64 -0
- package/adapters/vue/components/FdyAutocomplete.vue +174 -0
- package/adapters/vue/components/FdyCascade.vue +241 -0
- package/adapters/vue/components/FdyCfl.vue +354 -0
- package/adapters/vue/components/FdyChart.vue +89 -0
- package/adapters/vue/components/FdyCombo.vue +232 -0
- package/adapters/vue/components/FdyDateRange.vue +78 -0
- package/adapters/vue/components/FdyDatepicker.vue +333 -0
- package/adapters/vue/index.d.ts +44 -0
- package/adapters/vue/index.js +8 -0
- package/adapters/vue/useFreeday.js +44 -0
- package/adapters/vue/usePopover.ts +78 -0
- package/dist/freeday-autocomplete.js +135 -0
- package/dist/freeday-breakpoint.js +51 -0
- package/dist/freeday-carousel.js +111 -0
- package/dist/freeday-cascade.js +256 -0
- package/dist/freeday-cfl.js +213 -0
- package/dist/freeday-chart.js +403 -0
- package/dist/freeday-chip.js +83 -0
- package/dist/freeday-datepicker.js +321 -0
- package/dist/freeday-datetime.js +59 -0
- package/dist/freeday-drawer.js +43 -0
- package/dist/freeday-form.js +181 -0
- package/dist/freeday-mask.js +114 -0
- package/dist/freeday-menu.js +93 -0
- package/dist/freeday-popover.js +69 -0
- package/dist/freeday-rating.js +50 -0
- package/dist/freeday-select.js +194 -0
- package/dist/freeday-slider.js +34 -0
- package/dist/freeday-stepper.js +90 -0
- package/dist/freeday-table.js +475 -0
- package/dist/freeday-tabs.js +68 -0
- package/dist/freeday-timepicker.js +180 -0
- package/dist/freeday-toast.js +84 -0
- package/dist/freeday-tree.js +94 -0
- package/dist/freeday-upload.js +206 -0
- package/dist/freeday.bundle.css +1352 -0
- package/dist/freeday.css +1007 -0
- package/dist/freeday.js +3632 -0
- package/dist/freeday.tokens.css +343 -0
- package/package.json +72 -0
- package/src/base.css +20 -0
- package/src/components/.gitkeep +0 -0
- package/src/components/accordion.css +13 -0
- package/src/components/alert.css +18 -0
- package/src/components/app-shell.css +88 -0
- package/src/components/appbar.css +15 -0
- package/src/components/autocomplete.css +12 -0
- package/src/components/avatar.css +8 -0
- package/src/components/badge.css +27 -0
- package/src/components/breadcrumb.css +9 -0
- package/src/components/breakpoints.css +15 -0
- package/src/components/button.css +58 -0
- package/src/components/card.css +9 -0
- package/src/components/carousel.css +17 -0
- package/src/components/cascade.css +36 -0
- package/src/components/cfl.css +28 -0
- package/src/components/chart.css +55 -0
- package/src/components/chip.css +18 -0
- package/src/components/combo.css +27 -0
- package/src/components/datepicker.css +44 -0
- package/src/components/datetimepicker.css +8 -0
- package/src/components/description-list.css +6 -0
- package/src/components/divider.css +3 -0
- package/src/components/drawer.css +25 -0
- package/src/components/file-upload.css +34 -0
- package/src/components/filterbar.css +25 -0
- package/src/components/form-grid.css +8 -0
- package/src/components/input-group.css +22 -0
- package/src/components/input.css +13 -0
- package/src/components/kbd.css +2 -0
- package/src/components/menu.css +28 -0
- package/src/components/modal.css +22 -0
- package/src/components/pagination.css +8 -0
- package/src/components/progress.css +7 -0
- package/src/components/rating.css +10 -0
- package/src/components/selection.css +25 -0
- package/src/components/skeleton.css +7 -0
- package/src/components/slider.css +12 -0
- package/src/components/spinner.css +6 -0
- package/src/components/states.css +8 -0
- package/src/components/stepper.css +21 -0
- package/src/components/table.css +55 -0
- package/src/components/tabs.css +10 -0
- package/src/components/timeline.css +12 -0
- package/src/components/timepicker.css +22 -0
- package/src/components/toast.css +17 -0
- package/src/components/tooltip.css +6 -0
- package/src/components/tree.css +21 -0
- package/src/freeday-autocomplete.js +135 -0
- package/src/freeday-breakpoint.js +51 -0
- package/src/freeday-carousel.js +111 -0
- package/src/freeday-cascade.js +256 -0
- package/src/freeday-cfl.js +213 -0
- package/src/freeday-chart.js +403 -0
- package/src/freeday-chip.js +83 -0
- package/src/freeday-datepicker.js +321 -0
- package/src/freeday-datetime.js +59 -0
- package/src/freeday-drawer.js +43 -0
- package/src/freeday-form.js +181 -0
- package/src/freeday-mask.js +114 -0
- package/src/freeday-menu.js +93 -0
- package/src/freeday-popover.js +69 -0
- package/src/freeday-rating.js +50 -0
- package/src/freeday-select.js +194 -0
- package/src/freeday-slider.js +34 -0
- package/src/freeday-stepper.js +90 -0
- package/src/freeday-table.js +475 -0
- package/src/freeday-tabs.js +68 -0
- package/src/freeday-timepicker.js +180 -0
- package/src/freeday-toast.js +84 -0
- package/src/freeday-tree.js +94 -0
- package/src/freeday-upload.js +206 -0
- package/tokens/breakpoints.mjs +4 -0
- package/tokens/tokens.json +123 -0
|
@@ -0,0 +1,256 @@
|
|
|
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
|
+
})();
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/* Freeday — choose-from-list enhancer (optional, zero-dependency).
|
|
2
|
+
* Wires a searchable master-data picker dialog to read-only fields.
|
|
3
|
+
* Progressive enhancement over a native <dialog>; in a framework app, drive the
|
|
4
|
+
* dialog as a controlled component (fetchPage callback + server cache), not a store.
|
|
5
|
+
*
|
|
6
|
+
* Markup contract (see docs):
|
|
7
|
+
* - Field group: [data-fdy-cfl="<dialogId>"] — usually a `.fdy-input-group` holding a
|
|
8
|
+
* read-only `.fdy-input` and a trigger `<button class="fdy-input-group__btn">`.
|
|
9
|
+
* Optional `data-fdy-cfl-display="<key>"` picks which row dataset key fills the input
|
|
10
|
+
* (default "value"); optional `data-fdy-cfl-summary="{n} dipilih"` formats the multi label.
|
|
11
|
+
* - Standalone opener: [data-fdy-cfl-open="<dialogId>"] — opens the dialog with no bound field.
|
|
12
|
+
* - Dialog: <dialog id="<dialogId>" class="fdy-modal fdy-modal--cfl"> containing
|
|
13
|
+
* a `[data-fdy-cfl-search]` input, `.fdy-cfl__row` <tr> rows carrying `data-*` fields,
|
|
14
|
+
* a `[data-fdy-cfl-empty]` element, and (multi only) `[data-fdy-cfl-multiple]` on the
|
|
15
|
+
* dialog plus `[data-fdy-cfl-count]` and `[data-fdy-cfl-confirm]` in the footer.
|
|
16
|
+
*
|
|
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 — each a plain
|
|
19
|
+
* object copy of the row's dataset. Consumers fill sibling fields (code → name) from it.
|
|
20
|
+
*/
|
|
21
|
+
(function () {
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
function rowData(row) {
|
|
25
|
+
var out = {};
|
|
26
|
+
var ds = row.dataset;
|
|
27
|
+
for (var key in ds) { if (Object.prototype.hasOwnProperty.call(ds, key)) out[key] = ds[key]; }
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function rowSearchText(row) {
|
|
32
|
+
return (row.getAttribute('data-search') != null ? row.getAttribute('data-search') : row.textContent)
|
|
33
|
+
.toLowerCase();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function initDialog(dialog) {
|
|
37
|
+
if (dialog.dataset.fdyCflReady === '1') return;
|
|
38
|
+
dialog.dataset.fdyCflReady = '1';
|
|
39
|
+
|
|
40
|
+
var search = dialog.querySelector('[data-fdy-cfl-search]');
|
|
41
|
+
var emptyEl = dialog.querySelector('[data-fdy-cfl-empty]');
|
|
42
|
+
var countEl = dialog.querySelector('[data-fdy-cfl-count]');
|
|
43
|
+
var confirmBtn = dialog.querySelector('[data-fdy-cfl-confirm]');
|
|
44
|
+
var multiple = dialog.hasAttribute('data-fdy-cfl-multiple');
|
|
45
|
+
var rows = Array.prototype.slice.call(dialog.querySelectorAll('.fdy-cfl__row'));
|
|
46
|
+
|
|
47
|
+
var activeGroup = null; // field group that opened the dialog (null = standalone)
|
|
48
|
+
var trigger = null; // control to restore focus to on close
|
|
49
|
+
|
|
50
|
+
rows.forEach(function (row) {
|
|
51
|
+
row.setAttribute('tabindex', '-1');
|
|
52
|
+
if (multiple && !row.hasAttribute('aria-selected')) row.setAttribute('aria-selected', 'false');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function visibleRows() {
|
|
56
|
+
return rows.filter(function (r) { return !r.hidden; });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function applyFilter() {
|
|
60
|
+
var q = (search ? search.value : '').trim().toLowerCase();
|
|
61
|
+
var shown = 0;
|
|
62
|
+
rows.forEach(function (row) {
|
|
63
|
+
var match = !q || rowSearchText(row).indexOf(q) !== -1;
|
|
64
|
+
row.hidden = !match;
|
|
65
|
+
if (match) shown++;
|
|
66
|
+
});
|
|
67
|
+
if (emptyEl) emptyEl.hidden = shown !== 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function updateCount() {
|
|
71
|
+
if (!countEl) return;
|
|
72
|
+
var n = rows.filter(function (r) { return r.getAttribute('aria-selected') === 'true'; }).length;
|
|
73
|
+
countEl.textContent = n + ' dipilih';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function fillGroup(detail) {
|
|
77
|
+
if (!activeGroup) return;
|
|
78
|
+
var input = activeGroup.querySelector('.fdy-input');
|
|
79
|
+
if (input) {
|
|
80
|
+
if (detail.rows) {
|
|
81
|
+
var tpl = activeGroup.getAttribute('data-fdy-cfl-summary') || '{n} dipilih';
|
|
82
|
+
input.value = detail.rows.length ? tpl.replace('{n}', String(detail.rows.length)) : '';
|
|
83
|
+
} else {
|
|
84
|
+
var key = activeGroup.getAttribute('data-fdy-cfl-display') || 'value';
|
|
85
|
+
input.value = detail.row[key] != null ? detail.row[key] : (detail.row.value || '');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function emit(detail) {
|
|
91
|
+
(activeGroup || dialog).dispatchEvent(new CustomEvent('fdy-cfl-select', {
|
|
92
|
+
bubbles: true, detail: detail
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function commitSingle(row) {
|
|
97
|
+
var detail = { row: rowData(row) };
|
|
98
|
+
fillGroup(detail);
|
|
99
|
+
emit(detail);
|
|
100
|
+
dialog.close();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function toggleSelect(row) {
|
|
104
|
+
var next = row.getAttribute('aria-selected') !== 'true';
|
|
105
|
+
row.setAttribute('aria-selected', String(next));
|
|
106
|
+
var box = row.querySelector('input[type="checkbox"]');
|
|
107
|
+
if (box) box.checked = next;
|
|
108
|
+
updateCount();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function confirmMulti() {
|
|
112
|
+
var picked = rows.filter(function (r) { return r.getAttribute('aria-selected') === 'true'; });
|
|
113
|
+
var detail = { rows: picked.map(rowData) };
|
|
114
|
+
fillGroup(detail);
|
|
115
|
+
emit(detail);
|
|
116
|
+
dialog.close();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function focusRow(row) { if (row) row.focus(); }
|
|
120
|
+
|
|
121
|
+
function moveFrom(current, delta) {
|
|
122
|
+
var vis = visibleRows();
|
|
123
|
+
var i = vis.indexOf(current);
|
|
124
|
+
if (i === -1) { focusRow(vis[0]); return; }
|
|
125
|
+
var target = i + delta;
|
|
126
|
+
if (target < 0) { if (search) search.focus(); return; }
|
|
127
|
+
if (target >= vis.length) return;
|
|
128
|
+
focusRow(vis[target]);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (search) {
|
|
132
|
+
search.addEventListener('input', applyFilter);
|
|
133
|
+
search.addEventListener('keydown', function (e) {
|
|
134
|
+
if (e.key === 'ArrowDown') { e.preventDefault(); focusRow(visibleRows()[0]); }
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
dialog.addEventListener('click', function (e) {
|
|
139
|
+
var row = e.target.closest('.fdy-cfl__row');
|
|
140
|
+
if (!row || row.hidden) return;
|
|
141
|
+
if (multiple) toggleSelect(row); else commitSingle(row);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
dialog.addEventListener('keydown', function (e) {
|
|
145
|
+
var row = e.target.closest('.fdy-cfl__row');
|
|
146
|
+
if (!row) return;
|
|
147
|
+
var vis = visibleRows();
|
|
148
|
+
switch (e.key) {
|
|
149
|
+
case 'ArrowDown': e.preventDefault(); moveFrom(row, 1); break;
|
|
150
|
+
case 'ArrowUp': e.preventDefault(); moveFrom(row, -1); break;
|
|
151
|
+
case 'Home': e.preventDefault(); focusRow(vis[0]); break;
|
|
152
|
+
case 'End': e.preventDefault(); focusRow(vis[vis.length - 1]); break;
|
|
153
|
+
case 'Enter':
|
|
154
|
+
case ' ':
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
if (multiple) toggleSelect(row); else commitSingle(row);
|
|
157
|
+
break;
|
|
158
|
+
default: break;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (confirmBtn) confirmBtn.addEventListener('click', confirmMulti);
|
|
163
|
+
|
|
164
|
+
dialog.addEventListener('close', function () {
|
|
165
|
+
if (trigger && typeof trigger.focus === 'function') trigger.focus();
|
|
166
|
+
activeGroup = null;
|
|
167
|
+
trigger = null;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Opening API used by triggers below.
|
|
171
|
+
dialog.fdyCflOpen = function (group, opener) {
|
|
172
|
+
activeGroup = group || null;
|
|
173
|
+
trigger = opener || null;
|
|
174
|
+
if (search) search.value = '';
|
|
175
|
+
applyFilter();
|
|
176
|
+
updateCount();
|
|
177
|
+
if (typeof dialog.showModal === 'function') dialog.showModal(); else dialog.setAttribute('open', '');
|
|
178
|
+
if (search) search.focus();
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function initTriggers(context) {
|
|
183
|
+
var ctx = context || document;
|
|
184
|
+
|
|
185
|
+
Array.prototype.forEach.call(ctx.querySelectorAll('[data-fdy-cfl]'), function (group) {
|
|
186
|
+
if (group.dataset.fdyCflBound === '1') return;
|
|
187
|
+
var dialog = document.getElementById(group.getAttribute('data-fdy-cfl'));
|
|
188
|
+
if (!dialog) return;
|
|
189
|
+
initDialog(dialog);
|
|
190
|
+
var opener = group.querySelector('[data-fdy-cfl-trigger], .fdy-input-group__btn');
|
|
191
|
+
if (!opener) return;
|
|
192
|
+
group.dataset.fdyCflBound = '1';
|
|
193
|
+
opener.addEventListener('click', function () { dialog.fdyCflOpen(group, opener); });
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
Array.prototype.forEach.call(ctx.querySelectorAll('[data-fdy-cfl-open]'), function (opener) {
|
|
197
|
+
if (opener.dataset.fdyCflBound === '1') return;
|
|
198
|
+
var dialog = document.getElementById(opener.getAttribute('data-fdy-cfl-open'));
|
|
199
|
+
if (!dialog) return;
|
|
200
|
+
initDialog(dialog);
|
|
201
|
+
opener.dataset.fdyCflBound = '1';
|
|
202
|
+
opener.addEventListener('click', function () { dialog.fdyCflOpen(null, opener); });
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (document.readyState === 'loading') {
|
|
207
|
+
document.addEventListener('DOMContentLoaded', function () { initTriggers(); });
|
|
208
|
+
} else {
|
|
209
|
+
initTriggers();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
window.FreedayCfl = { init: initTriggers };
|
|
213
|
+
})();
|