mnfst 0.5.168 → 0.5.169
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/lib/manifest.combobox.js
CHANGED
|
@@ -497,13 +497,31 @@ function initializeComboboxPlugin() {
|
|
|
497
497
|
let suppressOpen = false;
|
|
498
498
|
function refocus() { suppressOpen = true; el.focus(); setTimeout(() => { suppressOpen = false; }, 0); }
|
|
499
499
|
|
|
500
|
+
// The model is written ONLY on explicit user selection; commits also fire a DOM
|
|
501
|
+
// change event so apps can save on events instead of watching the model.
|
|
502
|
+
// `dirty` = the user actually edited the text since focus — gates Enter's
|
|
503
|
+
// free-text commit so a stray Enter on an untouched field can't re-commit the
|
|
504
|
+
// display label (or, with the menu open, an auto-highlighted option) as a value.
|
|
505
|
+
let dirty = false;
|
|
506
|
+
let seeding = false; // init-time seeding is not a user commit — no change event
|
|
507
|
+
const fireChange = () => { if (!seeding) el.dispatchEvent(new Event('change', { bubbles: true })); };
|
|
508
|
+
|
|
500
509
|
function addValue(value, label) {
|
|
501
|
-
if (!multiple) {
|
|
510
|
+
if (!multiple) {
|
|
511
|
+
const same = selected[0] && String(selected[0].value) === String(value);
|
|
512
|
+
selected = [{ value, label }];
|
|
513
|
+
render();
|
|
514
|
+
dirty = false;
|
|
515
|
+
if (!same) { syncOut(); fireChange(); announce(label + ' selected'); }
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
502
518
|
if (isSelected(value)) return;
|
|
503
519
|
if (selected.length >= max) { announce('Maximum of ' + max + ' reached'); return; }
|
|
504
520
|
selected.push({ value, label });
|
|
505
521
|
render();
|
|
522
|
+
dirty = false;
|
|
506
523
|
syncOut();
|
|
524
|
+
fireChange();
|
|
507
525
|
announce(label + ' added');
|
|
508
526
|
}
|
|
509
527
|
function removeValue(value) {
|
|
@@ -513,6 +531,7 @@ function initializeComboboxPlugin() {
|
|
|
513
531
|
const [g] = selected.splice(i, 1);
|
|
514
532
|
render();
|
|
515
533
|
syncOut();
|
|
534
|
+
fireChange();
|
|
516
535
|
announce(g.label + ' removed');
|
|
517
536
|
if (menu && !isAsync) filter();
|
|
518
537
|
refocus();
|
|
@@ -634,7 +653,7 @@ function initializeComboboxPlugin() {
|
|
|
634
653
|
if (e.key === 'ArrowDown') { e.preventDefault(); moveActive(1); }
|
|
635
654
|
else if (e.key === 'ArrowUp') { e.preventDefault(); moveActive(-1); }
|
|
636
655
|
else if (e.key === 'Enter' || e.key === ' ') {
|
|
637
|
-
if (activeIndex >= 0 || activeIndex === -2) { e.preventDefault(); selectOption(liAt(activeIndex)); }
|
|
656
|
+
if (!e.repeat && menu.hasAttribute('data-kbd') && (activeIndex >= 0 || activeIndex === -2)) { e.preventDefault(); selectOption(liAt(activeIndex)); }
|
|
638
657
|
}
|
|
639
658
|
else if (e.key === 'Escape') { if (open) { e.preventDefault(); closeMenu(); } }
|
|
640
659
|
});
|
|
@@ -643,10 +662,15 @@ function initializeComboboxPlugin() {
|
|
|
643
662
|
if (e.key === 'ArrowDown' && menu) { e.preventDefault(); openMenu(); moveActive(1); }
|
|
644
663
|
else if (e.key === 'ArrowUp' && menu) { e.preventDefault(); moveActive(-1); }
|
|
645
664
|
else if (e.key === 'Enter') {
|
|
646
|
-
|
|
665
|
+
// Commit only on deliberate action: an option needs a keyboard-driven
|
|
666
|
+
// highlight (data-kbd — arrows or typing), free text needs an actual
|
|
667
|
+
// edit since focus (dirty). A stray Enter on an untouched field — key
|
|
668
|
+
// repeat, form submit, saving the record — must never write the model.
|
|
669
|
+
if (e.repeat) return;
|
|
670
|
+
if (menu && menu.matches(':popover-open') && menu.hasAttribute('data-kbd') && (activeIndex >= 0 || activeIndex === -2)) {
|
|
647
671
|
e.preventDefault();
|
|
648
672
|
selectOption(liAt(activeIndex));
|
|
649
|
-
} else if (!strict || !menu) {
|
|
673
|
+
} else if ((!strict || !menu) && dirty) {
|
|
650
674
|
if (commitText(el.value)) e.preventDefault();
|
|
651
675
|
}
|
|
652
676
|
}
|
|
@@ -658,19 +682,27 @@ function initializeComboboxPlugin() {
|
|
|
658
682
|
|
|
659
683
|
// Separators (and paste) commit from here, the moment one lands in the value.
|
|
660
684
|
el.addEventListener('input', () => {
|
|
685
|
+
dirty = true;
|
|
661
686
|
if (menu) menu.setAttribute('data-kbd', ''); // typing drives the highlight onto the first match
|
|
662
687
|
extractTokens();
|
|
663
688
|
if (isAsync) asyncRefresh(); else { openMenu(); filter(); }
|
|
664
689
|
});
|
|
665
690
|
|
|
666
|
-
el.addEventListener('focus', () => { if (!suppressOpen) openForEntry(true); });
|
|
691
|
+
el.addEventListener('focus', () => { dirty = false; if (!suppressOpen) openForEntry(true); });
|
|
667
692
|
// Reopen on click even when the field is already focused (focus won't re-fire).
|
|
668
693
|
el.addEventListener('mousedown', () => { if (menu && !menu.matches(':popover-open') && !suppressOpen) openForEntry(false); });
|
|
669
694
|
}
|
|
670
695
|
|
|
671
|
-
// Close when focus leaves the field entirely (Tab away).
|
|
696
|
+
// Close when focus leaves the field entirely (Tab away). A committed single
|
|
697
|
+
// field also snaps its display back to the selection's label — abandoned typing
|
|
698
|
+
// never commits, and the model is left untouched.
|
|
672
699
|
el.addEventListener('blur', () => setTimeout(() => {
|
|
673
|
-
if (
|
|
700
|
+
if (wrap.contains(document.activeElement) || (menu && menu.contains(document.activeElement))) return;
|
|
701
|
+
closeMenu();
|
|
702
|
+
if (!multiple && !chips && !editorNone && selected[0] && el.value !== selected[0].label) {
|
|
703
|
+
el.value = selected[0].label;
|
|
704
|
+
dirty = false;
|
|
705
|
+
}
|
|
674
706
|
}, 0));
|
|
675
707
|
|
|
676
708
|
// Click empty shell → focus the trigger
|
|
@@ -753,7 +785,9 @@ function initializeComboboxPlugin() {
|
|
|
753
785
|
if (!modelExpr && !adopt && !editorNone && chips && el.value) {
|
|
754
786
|
const seeds = el.value.split(/[,\n;]+/).map(s => s.trim()).filter(Boolean);
|
|
755
787
|
el.value = '';
|
|
788
|
+
seeding = true;
|
|
756
789
|
seeds.forEach(s => commitText(s));
|
|
790
|
+
seeding = false;
|
|
757
791
|
}
|
|
758
792
|
// In chips/multiple the editor is a typing buffer. Clear value AND remove the
|
|
759
793
|
// attribute: mnfst-render serializes the attribute, so leaving it bakes seed text
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"manifest.code.js": "sha384-G3MNfvQRWWY+gm4SGGrSsgQbDeil9MP+ON2DTk6Rcq9Nsex8woHMDt5EJHrFH0Nj",
|
|
8
8
|
"manifest.color.js": "sha384-6Rv3LxyTcZNjrhtayQfqRdCx0uSZ4BiEbgEI98I62eTvp8Aw7LBIoNJ0Je1oktwL",
|
|
9
9
|
"manifest.colorpicker.js": "sha384-N5ezEwgKrDodV4YdURxhdyZKmg1n/QhqvmAC4SEG03mw1pEAZAA8Gphp+NLAOxts",
|
|
10
|
-
"manifest.combobox.js": "sha384-
|
|
10
|
+
"manifest.combobox.js": "sha384-ZPOkx4hKGlcrOqeb3MmWLyBfjZXlnkP47Yc9VLE7yHds6dXRqMeB5DPFeJl3NZS/",
|
|
11
11
|
"manifest.components.js": "sha384-eZDib+RFdwd9mOUdi6o53eNS0LBlEEkZPDegxtkAcE+GrGIFxsuB4O0ymfixGNfM",
|
|
12
12
|
"manifest.data.js": "sha384-3lLvGnqcQr4Ium80MdaQTr8RytjVbs5rnxd0q41BF3ppcoQccMFAcvgFSyZP/0sl",
|
|
13
13
|
"manifest.datepicker.js": "sha384-e6rJI0BxuE+AyI96/u6XoeOstsxLuQTO6+wYSr6rm2aKhOP2APM2xJqTz//0v4Pw",
|