selective-ui 1.0.3 → 1.0.5
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/dist/selective-ui.esm.js +587 -115
- package/dist/selective-ui.esm.js.map +1 -1
- package/dist/selective-ui.esm.min.js +1 -1
- package/dist/selective-ui.esm.min.js.br +0 -0
- package/dist/selective-ui.min.js +2 -2
- package/dist/selective-ui.min.js.br +0 -0
- package/dist/selective-ui.umd.js +587 -115
- package/dist/selective-ui.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/js/adapter/mixed-adapter.js +6 -4
- package/src/js/components/selectbox.js +69 -12
- package/src/js/core/model-manager.js +82 -7
- package/src/js/core/search-controller.js +82 -0
- package/src/js/index.js +1 -1
- package/src/js/services/refresher.js +8 -0
- package/src/js/services/select-observer.js +72 -15
- package/src/js/views/option-view.js +267 -76
package/dist/selective-ui.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Selective UI v1.0.
|
|
1
|
+
/*! Selective UI v1.0.5 | MIT License */
|
|
2
2
|
/**
|
|
3
3
|
* @class
|
|
4
4
|
*/
|
|
@@ -789,6 +789,14 @@ class Refresher {
|
|
|
789
789
|
|
|
790
790
|
let width = `${select.offsetWidth}px`,
|
|
791
791
|
height = `${select.offsetHeight}px`;
|
|
792
|
+
|
|
793
|
+
const getCStyle = getComputedStyle(select);
|
|
794
|
+
if (width == "0px" && getCStyle.width != "auto") {
|
|
795
|
+
width = getCStyle.width;
|
|
796
|
+
}
|
|
797
|
+
if (height == "0px" && getCStyle.height != "auto") {
|
|
798
|
+
height = getCStyle.height;
|
|
799
|
+
}
|
|
792
800
|
|
|
793
801
|
if (cfgWidth > 0) {
|
|
794
802
|
width = options.width;
|
|
@@ -1388,58 +1396,206 @@ class OptionView extends View {
|
|
|
1388
1396
|
/** @type {OptionViewResult} */
|
|
1389
1397
|
view;
|
|
1390
1398
|
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1399
|
+
#config = null;
|
|
1400
|
+
#configProxy = null;
|
|
1401
|
+
#isRendered = false;
|
|
1402
|
+
|
|
1403
|
+
/**
|
|
1404
|
+
* Initializes the OptionView with a parent container and sets up the reactive config proxy.
|
|
1405
|
+
* The proxy enables partial DOM updates when config properties change after initial render.
|
|
1406
|
+
*
|
|
1407
|
+
* @param {HTMLElement} parent - The parent element into which this view will be mounted.
|
|
1408
|
+
*/
|
|
1409
|
+
constructor(parent) {
|
|
1410
|
+
super(parent);
|
|
1411
|
+
this.#setupConfigProxy();
|
|
1412
|
+
}
|
|
1394
1413
|
|
|
1395
1414
|
/**
|
|
1396
|
-
*
|
|
1397
|
-
*
|
|
1415
|
+
* Creates the internal configuration object and wraps it with a Proxy.
|
|
1416
|
+
* The proxy intercepts property assignments and, if the view is rendered,
|
|
1417
|
+
* applies only the necessary DOM changes for the updated property.
|
|
1418
|
+
* No DOM mutations occur before the first render.
|
|
1419
|
+
*/
|
|
1420
|
+
#setupConfigProxy() {
|
|
1421
|
+
const self = this;
|
|
1422
|
+
|
|
1423
|
+
this.#config = {
|
|
1424
|
+
isMultiple: false,
|
|
1425
|
+
hasImage: false,
|
|
1426
|
+
imagePosition: 'right',
|
|
1427
|
+
imageWidth: '60px',
|
|
1428
|
+
imageHeight: '60px',
|
|
1429
|
+
imageBorderRadius: '4px',
|
|
1430
|
+
labelValign: 'center',
|
|
1431
|
+
labelHalign: 'left'
|
|
1432
|
+
};
|
|
1433
|
+
|
|
1434
|
+
this.#configProxy = new Proxy(this.#config, {
|
|
1435
|
+
set(target, prop, value) {
|
|
1436
|
+
const oldValue = target[prop];
|
|
1437
|
+
|
|
1438
|
+
if (oldValue !== value) {
|
|
1439
|
+
target[prop] = value;
|
|
1440
|
+
|
|
1441
|
+
if (self.#isRendered) {
|
|
1442
|
+
self.#applyPartialChange(prop, value, oldValue);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
return true;
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
/**
|
|
1451
|
+
* Indicates whether the option supports multiple selection (checkbox) instead of single (radio).
|
|
1452
|
+
*
|
|
1453
|
+
* @returns {boolean} True if multiple selection is enabled; otherwise false.
|
|
1454
|
+
*/
|
|
1455
|
+
get isMultiple() {
|
|
1456
|
+
return this.#config.isMultiple;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
* Enables or disables multiple selection mode.
|
|
1462
|
+
* When rendered, toggles the root CSS class and switches the input type between 'checkbox' and 'radio'.
|
|
1463
|
+
*
|
|
1464
|
+
* @param {boolean} value - True to enable multiple selection; false for single selection.
|
|
1465
|
+
*/
|
|
1466
|
+
set isMultiple(value) {
|
|
1467
|
+
this.#configProxy.isMultiple = !!value;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
/**
|
|
1471
|
+
* Indicates whether the option includes an image block alongside the label.
|
|
1472
|
+
*
|
|
1473
|
+
* @returns {boolean} True if an image is displayed; otherwise false.
|
|
1474
|
+
*/
|
|
1475
|
+
get hasImage() {
|
|
1476
|
+
return this.#config.hasImage;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
|
|
1480
|
+
/**
|
|
1481
|
+
* Shows or hides the image block for the option.
|
|
1482
|
+
* When rendered, toggles related CSS classes and creates/removes the image element accordingly.
|
|
1483
|
+
*
|
|
1484
|
+
* @param {boolean} value - True to show the image; false to hide it.
|
|
1485
|
+
*/
|
|
1486
|
+
set hasImage(value) {
|
|
1487
|
+
this.#configProxy.hasImage = !!value;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
/**
|
|
1491
|
+
* Provides reactive access to the entire option configuration via a Proxy.
|
|
1492
|
+
* Mutating properties on this object will trigger partial DOM updates when rendered.
|
|
1493
|
+
*
|
|
1494
|
+
* @returns {object} The proxied configuration object.
|
|
1495
|
+
*/
|
|
1496
|
+
get optionConfig() {
|
|
1497
|
+
return this.#configProxy;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
|
|
1501
|
+
/**
|
|
1502
|
+
* Applies a set of configuration changes in batch.
|
|
1503
|
+
* Only properties that differ from the current config are updated.
|
|
1504
|
+
* When rendered, each changed property triggers a targeted DOM update via the proxy.
|
|
1505
|
+
*
|
|
1506
|
+
* @param {object} config - Partial configuration object.
|
|
1507
|
+
* @param {string} [config.imageWidth] - CSS width of the image (e.g., '60px').
|
|
1508
|
+
* @param {string} [config.imageHeight] - CSS height of the image (e.g., '60px').
|
|
1509
|
+
* @param {string} [config.imageBorderRadius] - CSS border-radius for the image (e.g., '4px').
|
|
1510
|
+
* @param {'top'|'right'|'bottom'|'left'} [config.imagePosition] - Position of the image relative to the label.
|
|
1511
|
+
* @param {'top'|'center'|'bottom'} [config.labelValign] - Vertical alignment of the label.
|
|
1512
|
+
* @param {'left'|'center'|'right'} [config.labelHalign] - Horizontal alignment of the label.
|
|
1513
|
+
*/
|
|
1514
|
+
set optionConfig(config) {
|
|
1515
|
+
if (!config) return;
|
|
1516
|
+
|
|
1517
|
+
const changes = {};
|
|
1518
|
+
let hasChanges = false;
|
|
1519
|
+
|
|
1520
|
+
if (config.imageWidth !== undefined && config.imageWidth !== this.#config.imageWidth) {
|
|
1521
|
+
changes.imageWidth = config.imageWidth;
|
|
1522
|
+
hasChanges = true;
|
|
1523
|
+
}
|
|
1524
|
+
if (config.imageHeight !== undefined && config.imageHeight !== this.#config.imageHeight) {
|
|
1525
|
+
changes.imageHeight = config.imageHeight;
|
|
1526
|
+
hasChanges = true;
|
|
1527
|
+
}
|
|
1528
|
+
if (config.imageBorderRadius !== undefined && config.imageBorderRadius !== this.#config.imageBorderRadius) {
|
|
1529
|
+
changes.imageBorderRadius = config.imageBorderRadius;
|
|
1530
|
+
hasChanges = true;
|
|
1531
|
+
}
|
|
1532
|
+
if (config.imagePosition !== undefined && config.imagePosition !== this.#config.imagePosition) {
|
|
1533
|
+
changes.imagePosition = config.imagePosition;
|
|
1534
|
+
hasChanges = true;
|
|
1535
|
+
}
|
|
1536
|
+
if (config.labelValign !== undefined && config.labelValign !== this.#config.labelValign) {
|
|
1537
|
+
changes.labelValign = config.labelValign;
|
|
1538
|
+
hasChanges = true;
|
|
1539
|
+
}
|
|
1540
|
+
if (config.labelHalign !== undefined && config.labelHalign !== this.#config.labelHalign) {
|
|
1541
|
+
changes.labelHalign = config.labelHalign;
|
|
1542
|
+
hasChanges = true;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
if (hasChanges) {
|
|
1546
|
+
Object.assign(this.#configProxy, changes);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
/**
|
|
1551
|
+
* Renders the option view into the parent element.
|
|
1552
|
+
* Builds the DOM structure (input, optional image, label) based on current config,
|
|
1553
|
+
* assigns classes and ARIA attributes, mounts via Libs.mountView, and marks as rendered
|
|
1554
|
+
* to allow future incremental updates through the config proxy.
|
|
1398
1555
|
*/
|
|
1399
1556
|
render() {
|
|
1400
1557
|
const viewClass = ["selective-ui-option-view"];
|
|
1401
1558
|
const opt_id = Libs.randomString(7);
|
|
1402
1559
|
const inputID = `option_${opt_id}`;
|
|
1403
1560
|
|
|
1404
|
-
if (this.isMultiple) {
|
|
1561
|
+
if (this.#config.isMultiple) {
|
|
1405
1562
|
viewClass.push("multiple");
|
|
1406
1563
|
}
|
|
1407
|
-
|
|
1408
|
-
if (this.hasImage) {
|
|
1564
|
+
if (this.#config.hasImage) {
|
|
1409
1565
|
viewClass.push("has-image");
|
|
1410
|
-
viewClass.push(`image-${this.
|
|
1566
|
+
viewClass.push(`image-${this.#config.imagePosition}`);
|
|
1411
1567
|
}
|
|
1412
1568
|
|
|
1413
1569
|
const childStructure = {
|
|
1414
1570
|
OptionInput: {
|
|
1415
1571
|
tag: {
|
|
1416
1572
|
node: "input",
|
|
1417
|
-
type: this.isMultiple ? "checkbox" : "radio",
|
|
1573
|
+
type: this.#config.isMultiple ? "checkbox" : "radio",
|
|
1418
1574
|
classList: "allow-choice",
|
|
1419
1575
|
id: inputID
|
|
1420
1576
|
}
|
|
1421
1577
|
},
|
|
1422
|
-
...(this.hasImage && {
|
|
1578
|
+
...(this.#config.hasImage && {
|
|
1423
1579
|
OptionImage: {
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1580
|
+
tag: {
|
|
1581
|
+
node: "img",
|
|
1582
|
+
classList: "option-image",
|
|
1583
|
+
style: {
|
|
1584
|
+
width: this.#config.imageWidth,
|
|
1585
|
+
height: this.#config.imageHeight,
|
|
1586
|
+
borderRadius: this.#config.imageBorderRadius
|
|
1587
|
+
}
|
|
1431
1588
|
}
|
|
1432
1589
|
}
|
|
1433
|
-
}
|
|
1434
1590
|
}),
|
|
1435
1591
|
OptionLabel: {
|
|
1436
1592
|
tag: {
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1593
|
+
node: "label",
|
|
1594
|
+
htmlFor: inputID,
|
|
1595
|
+
classList: [
|
|
1596
|
+
`align-vertical-${this.#config.labelValign}`,
|
|
1597
|
+
`align-horizontal-${this.#config.labelHalign}`
|
|
1598
|
+
]
|
|
1443
1599
|
},
|
|
1444
1600
|
child: {
|
|
1445
1601
|
LabelContent: { tag: { node: "div" } }
|
|
@@ -1462,72 +1618,115 @@ class OptionView extends View {
|
|
|
1462
1618
|
});
|
|
1463
1619
|
|
|
1464
1620
|
this.parent.appendChild(this.view.view);
|
|
1465
|
-
|
|
1466
|
-
this.applyConfigToDOM();
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
/**
|
|
1470
|
-
* Refreshes the option view by reapplying configuration (classes, alignments, image styles).
|
|
1471
|
-
*/
|
|
1472
|
-
update() {
|
|
1473
|
-
this.applyConfigToDOM();
|
|
1621
|
+
this.#isRendered = true;
|
|
1474
1622
|
}
|
|
1475
1623
|
|
|
1476
1624
|
/**
|
|
1477
|
-
* Applies
|
|
1478
|
-
*
|
|
1479
|
-
*
|
|
1480
|
-
*
|
|
1625
|
+
* Applies a targeted DOM update for a single configuration property change.
|
|
1626
|
+
* Safely updates classes, attributes, styles, and child elements without re-rendering the whole view.
|
|
1627
|
+
*
|
|
1628
|
+
* @param {string | symbol} prop - The name of the changed configuration property.
|
|
1629
|
+
* @param {any} newValue - The new value assigned to the property.
|
|
1630
|
+
* @param {any} oldValue - The previous value of the property.
|
|
1481
1631
|
*/
|
|
1482
|
-
|
|
1632
|
+
#applyPartialChange(prop, newValue, oldValue) {
|
|
1483
1633
|
const v = this.view;
|
|
1484
1634
|
if (!v || !v.view) return;
|
|
1485
1635
|
|
|
1486
|
-
const root
|
|
1636
|
+
const root = v.view;
|
|
1487
1637
|
const input = v.tags?.OptionInput;
|
|
1488
1638
|
const label = v.tags?.OptionLabel;
|
|
1489
|
-
const isMultiple = !!this.isMultiple;
|
|
1490
|
-
const hasImage = !!this.hasImage;
|
|
1491
|
-
const imagePos = this.optionConfig?.imagePosition || 'right';
|
|
1492
|
-
const imageWidth = this.optionConfig?.imageWidth || '60px';
|
|
1493
|
-
const imageHeight = this.optionConfig?.imageHeight || '60px';
|
|
1494
|
-
const imageRadius = this.optionConfig?.imageBorderRadius || '4px';
|
|
1495
|
-
const vAlign = this.optionConfig?.labelValign || 'center';
|
|
1496
|
-
const hAlign = this.optionConfig?.labelHalign || 'left';
|
|
1497
1639
|
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1640
|
+
switch(prop) {
|
|
1641
|
+
case 'isMultiple':
|
|
1642
|
+
root.classList.toggle('multiple', newValue);
|
|
1643
|
+
|
|
1644
|
+
if (input && input.type !== (newValue ? 'checkbox' : 'radio')) {
|
|
1645
|
+
input.type = newValue ? 'checkbox' : 'radio';
|
|
1646
|
+
}
|
|
1647
|
+
break;
|
|
1504
1648
|
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1649
|
+
case 'hasImage':
|
|
1650
|
+
root.classList.toggle('has-image', newValue);
|
|
1651
|
+
|
|
1652
|
+
if (newValue) {
|
|
1653
|
+
root.classList.add(`image-${this.#config.imagePosition}`);
|
|
1654
|
+
this.#createImage();
|
|
1655
|
+
} else {
|
|
1656
|
+
root.className = root.className.replace(/image-(top|right|bottom|left)/g, '').trim();
|
|
1657
|
+
const image = v.tags?.OptionImage;
|
|
1658
|
+
if (image) {
|
|
1659
|
+
image.remove();
|
|
1660
|
+
v.tags.OptionImage = null;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
break;
|
|
1509
1664
|
|
|
1510
|
-
|
|
1511
|
-
|
|
1665
|
+
case 'imagePosition':
|
|
1666
|
+
if (this.#config.hasImage) {
|
|
1667
|
+
root.className = root.className.replace(/image-(top|right|bottom|left)/g, '').trim();
|
|
1668
|
+
root.classList.add(`image-${newValue}`);
|
|
1669
|
+
}
|
|
1670
|
+
break;
|
|
1671
|
+
|
|
1672
|
+
case 'imageWidth':
|
|
1673
|
+
case 'imageHeight':
|
|
1674
|
+
case 'imageBorderRadius':
|
|
1675
|
+
const image = v.tags?.OptionImage;
|
|
1676
|
+
if (image) {
|
|
1677
|
+
const styleProp = {
|
|
1678
|
+
'imageWidth': 'width',
|
|
1679
|
+
'imageHeight': 'height',
|
|
1680
|
+
'imageBorderRadius': 'borderRadius'
|
|
1681
|
+
}[prop];
|
|
1682
|
+
|
|
1683
|
+
if (image.style[styleProp] !== newValue) {
|
|
1684
|
+
image.style[styleProp] = newValue;
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
break;
|
|
1688
|
+
|
|
1689
|
+
case 'labelValign':
|
|
1690
|
+
case 'labelHalign':
|
|
1691
|
+
if (label) {
|
|
1692
|
+
const newClass = `align-vertical-${this.#config.labelValign} align-horizontal-${this.#config.labelHalign}`;
|
|
1693
|
+
if (label.className !== newClass) {
|
|
1694
|
+
label.className = newClass;
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
break;
|
|
1512
1698
|
}
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
/**
|
|
1702
|
+
* Creates the <img> element for the option on demand and inserts it into the DOM.
|
|
1703
|
+
* Skips creation if the view or root is missing, or if an image already exists.
|
|
1704
|
+
* The image receives configured styles (width, height, borderRadius) and is placed
|
|
1705
|
+
* before the label if present; otherwise appended to the root. Updates `v.tags.OptionImage`.
|
|
1706
|
+
*/
|
|
1707
|
+
#createImage() {
|
|
1708
|
+
const v = this.view;
|
|
1709
|
+
if (!v || !v.view) return;
|
|
1513
1710
|
|
|
1514
1711
|
let image = v.tags?.OptionImage;
|
|
1515
|
-
if (
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1712
|
+
if (image) return;
|
|
1713
|
+
|
|
1714
|
+
const root = v.view;
|
|
1715
|
+
const label = v.tags?.OptionLabel;
|
|
1716
|
+
|
|
1717
|
+
image = document.createElement('img');
|
|
1718
|
+
image.className = 'option-image';
|
|
1719
|
+
image.style.width = this.#config.imageWidth;
|
|
1720
|
+
image.style.height = this.#config.imageHeight;
|
|
1721
|
+
image.style.borderRadius = this.#config.imageBorderRadius;
|
|
1722
|
+
|
|
1723
|
+
if (label && label.parentElement) {
|
|
1724
|
+
root.insertBefore(image, label);
|
|
1725
|
+
} else {
|
|
1726
|
+
root.appendChild(image);
|
|
1530
1727
|
}
|
|
1728
|
+
|
|
1729
|
+
v.tags.OptionImage = image;
|
|
1531
1730
|
}
|
|
1532
1731
|
}
|
|
1533
1732
|
|
|
@@ -2083,6 +2282,8 @@ class ModelManager {
|
|
|
2083
2282
|
/** @type {RecyclerViewContract<TAdapter>} */
|
|
2084
2283
|
#privRecyclerViewHandle;
|
|
2085
2284
|
|
|
2285
|
+
#lastFingerprint = null;
|
|
2286
|
+
|
|
2086
2287
|
options = null;
|
|
2087
2288
|
|
|
2088
2289
|
/**
|
|
@@ -2112,6 +2313,48 @@ class ModelManager {
|
|
|
2112
2313
|
this.#privRecyclerView = recyclerView;
|
|
2113
2314
|
}
|
|
2114
2315
|
|
|
2316
|
+
/**
|
|
2317
|
+
* Checks whether the provided model data differs from the last recorded fingerprint.
|
|
2318
|
+
* Computes a new fingerprint and compares it to the previous one; if different,
|
|
2319
|
+
* updates the stored fingerprint and returns true, otherwise returns false.
|
|
2320
|
+
*
|
|
2321
|
+
* @param {Array<HTMLOptionElement|HTMLOptGroupElement>} modelData - The current model data (options/optgroups).
|
|
2322
|
+
* @returns {boolean} True if there are real changes; false otherwise.
|
|
2323
|
+
*/
|
|
2324
|
+
hasRealChanges(modelData) {
|
|
2325
|
+
const newFingerprint = this.#createFingerprint(modelData);
|
|
2326
|
+
const hasChanges = newFingerprint !== this.#lastFingerprint;
|
|
2327
|
+
|
|
2328
|
+
if (hasChanges) {
|
|
2329
|
+
this.#lastFingerprint = newFingerprint;
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
return hasChanges;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
/**
|
|
2336
|
+
* Produces a stable string fingerprint for the given model data.
|
|
2337
|
+
* For <optgroup>, includes the label and a pipe-joined hash of its child options
|
|
2338
|
+
* (value:text:selected). For plain <option>, includes its value, text, and selected state.
|
|
2339
|
+
* The entire list is joined by '||' to form the final fingerprint.
|
|
2340
|
+
*
|
|
2341
|
+
* @param {Array<HTMLOptionElement|HTMLOptGroupElement>} modelData - The current model data to fingerprint.
|
|
2342
|
+
* @returns {string} A deterministic fingerprint representing the structure and selection state.
|
|
2343
|
+
*/
|
|
2344
|
+
#createFingerprint(modelData) {
|
|
2345
|
+
return modelData.map(item => {
|
|
2346
|
+
if (item.tagName === "OPTGROUP") {
|
|
2347
|
+
const optionsHash = Array.from(item.children)
|
|
2348
|
+
.map((/** @type {HTMLOptionElement} */ opt) => `${opt.value}:${opt.text}:${opt.selected}`)
|
|
2349
|
+
.join('|');
|
|
2350
|
+
return `G:${item.label}:${optionsHash}`;
|
|
2351
|
+
} else {
|
|
2352
|
+
const oItem = /** @type {HTMLOptionElement} */ (item);
|
|
2353
|
+
return `O:${oItem.value}:${oItem.text}:${oItem.selected}`;
|
|
2354
|
+
}
|
|
2355
|
+
}).join('||');
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2115
2358
|
/**
|
|
2116
2359
|
* Builds model instances (GroupModel/OptionModel) from raw <optgroup>/<option> elements.
|
|
2117
2360
|
* Preserves grouping relationships and returns the structured list.
|
|
@@ -2152,6 +2395,8 @@ class ModelManager {
|
|
|
2152
2395
|
* @param {Array<HTMLOptGroupElement|HTMLOptionElement>} modelData - New source elements to rebuild models from.
|
|
2153
2396
|
*/
|
|
2154
2397
|
replace(modelData) {
|
|
2398
|
+
this.#lastFingerprint = null;
|
|
2399
|
+
|
|
2155
2400
|
this.createModelResources(modelData);
|
|
2156
2401
|
|
|
2157
2402
|
if (this.#privAdapterHandle) {
|
|
@@ -2196,6 +2441,10 @@ class ModelManager {
|
|
|
2196
2441
|
* @param {Array<HTMLOptGroupElement|HTMLOptionElement>} modelData - Fresh DOM elements reflecting the latest state.
|
|
2197
2442
|
*/
|
|
2198
2443
|
update(modelData) {
|
|
2444
|
+
if (!this.hasRealChanges(modelData)) {
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2199
2448
|
const oldModels = this.#privModelList;
|
|
2200
2449
|
const newModels = [];
|
|
2201
2450
|
|
|
@@ -2206,20 +2455,29 @@ class ModelManager {
|
|
|
2206
2455
|
if (model instanceof GroupModel) {
|
|
2207
2456
|
oldGroupMap.set(model.label, model);
|
|
2208
2457
|
} else if (model instanceof OptionModel) {
|
|
2209
|
-
|
|
2458
|
+
const key = `${model.value}::${model.textContent}`;
|
|
2459
|
+
oldOptionMap.set(key, model);
|
|
2210
2460
|
}
|
|
2211
2461
|
});
|
|
2212
2462
|
|
|
2213
2463
|
let currentGroup = null;
|
|
2214
2464
|
let position = 0;
|
|
2465
|
+
const changesToApply = [];
|
|
2215
2466
|
|
|
2216
2467
|
modelData.forEach((data, index) => {
|
|
2217
2468
|
if (data.tagName === "OPTGROUP") {
|
|
2218
2469
|
let dataVset = /** @type {HTMLOptGroupElement} */ (data);
|
|
2219
|
-
const existingGroup = oldGroupMap.get(dataVset.label);
|
|
2470
|
+
const existingGroup = /** @type {GroupModel} */ (oldGroupMap.get(dataVset.label));
|
|
2220
2471
|
|
|
2221
2472
|
if (existingGroup) {
|
|
2222
|
-
existingGroup.
|
|
2473
|
+
const hasLabelChange = existingGroup.label !== dataVset.label;
|
|
2474
|
+
|
|
2475
|
+
if (hasLabelChange) {
|
|
2476
|
+
changesToApply.push(() => {
|
|
2477
|
+
existingGroup.update(dataVset);
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2223
2481
|
existingGroup.position = position;
|
|
2224
2482
|
existingGroup.items = [];
|
|
2225
2483
|
currentGroup = existingGroup;
|
|
@@ -2234,11 +2492,21 @@ class ModelManager {
|
|
|
2234
2492
|
}
|
|
2235
2493
|
else if (data.tagName === "OPTION") {
|
|
2236
2494
|
let dataVset = /** @type {HTMLOptionElement} */ (data);
|
|
2237
|
-
const
|
|
2495
|
+
const key = `${dataVset.value}::${dataVset.text}`;
|
|
2496
|
+
const existingOption = /** @type {OptionModel} */ (oldOptionMap.get(key));
|
|
2238
2497
|
|
|
2239
2498
|
if (existingOption) {
|
|
2240
|
-
existingOption.
|
|
2241
|
-
existingOption.position
|
|
2499
|
+
const hasSelectedChange = existingOption.selected !== dataVset.selected;
|
|
2500
|
+
const hasPositionChange = existingOption.position !== position;
|
|
2501
|
+
|
|
2502
|
+
if (hasSelectedChange || hasPositionChange) {
|
|
2503
|
+
changesToApply.push(() => {
|
|
2504
|
+
existingOption.update(dataVset);
|
|
2505
|
+
existingOption.position = position;
|
|
2506
|
+
});
|
|
2507
|
+
} else {
|
|
2508
|
+
existingOption.position = position;
|
|
2509
|
+
}
|
|
2242
2510
|
|
|
2243
2511
|
if (dataVset["__parentGroup"] && currentGroup) {
|
|
2244
2512
|
currentGroup.addItem(existingOption);
|
|
@@ -2248,7 +2516,7 @@ class ModelManager {
|
|
|
2248
2516
|
newModels.push(existingOption);
|
|
2249
2517
|
}
|
|
2250
2518
|
|
|
2251
|
-
oldOptionMap.delete(
|
|
2519
|
+
oldOptionMap.delete(key);
|
|
2252
2520
|
} else {
|
|
2253
2521
|
const newOption = new OptionModel(this.options, dataVset);
|
|
2254
2522
|
newOption.position = position;
|
|
@@ -2264,6 +2532,12 @@ class ModelManager {
|
|
|
2264
2532
|
}
|
|
2265
2533
|
});
|
|
2266
2534
|
|
|
2535
|
+
if (changesToApply.length > 0) {
|
|
2536
|
+
requestAnimationFrame(() => {
|
|
2537
|
+
changesToApply.forEach(change => change());
|
|
2538
|
+
});
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2267
2541
|
oldGroupMap.forEach(removedGroup => {
|
|
2268
2542
|
if (removedGroup.view) {
|
|
2269
2543
|
removedGroup.view.getView()?.remove();
|
|
@@ -2613,8 +2887,6 @@ class MixedAdapter extends Adapter {
|
|
|
2613
2887
|
|
|
2614
2888
|
if (!optionModel.isInit) {
|
|
2615
2889
|
super.onViewHolder(optionModel, optionViewer, position);
|
|
2616
|
-
} else {
|
|
2617
|
-
optionViewer.update();
|
|
2618
2890
|
}
|
|
2619
2891
|
|
|
2620
2892
|
optionModel.view = optionViewer;
|
|
@@ -2622,8 +2894,12 @@ class MixedAdapter extends Adapter {
|
|
|
2622
2894
|
if (optionModel.hasImage) {
|
|
2623
2895
|
const imageTag = optionViewer.getTag("OptionImage");
|
|
2624
2896
|
if (imageTag) {
|
|
2625
|
-
imageTag.src
|
|
2626
|
-
|
|
2897
|
+
if (imageTag.src != optionModel.imageSrc) {
|
|
2898
|
+
imageTag.src = optionModel.imageSrc;
|
|
2899
|
+
}
|
|
2900
|
+
if (imageTag.alt != optionModel.text) {
|
|
2901
|
+
imageTag.alt = optionModel.text;
|
|
2902
|
+
}
|
|
2627
2903
|
}
|
|
2628
2904
|
}
|
|
2629
2905
|
|
|
@@ -4184,6 +4460,88 @@ class SearchController {
|
|
|
4184
4460
|
return !(!this.#ajaxConfig);
|
|
4185
4461
|
}
|
|
4186
4462
|
|
|
4463
|
+
/**
|
|
4464
|
+
* Load specific options by their values from server
|
|
4465
|
+
* @param {string|string[]} values - Values to load
|
|
4466
|
+
* @returns {Promise<{success: boolean, items: Array, message?: string}>}
|
|
4467
|
+
*/
|
|
4468
|
+
async loadByValues(values) {
|
|
4469
|
+
if (!this.#ajaxConfig) {
|
|
4470
|
+
return { success: false, items: [], message: "Ajax not configured" };
|
|
4471
|
+
}
|
|
4472
|
+
|
|
4473
|
+
const valuesArray = Array.isArray(values) ? values : [values];
|
|
4474
|
+
if (valuesArray.length === 0) {
|
|
4475
|
+
return { success: true, items: [] };
|
|
4476
|
+
}
|
|
4477
|
+
|
|
4478
|
+
try {
|
|
4479
|
+
const cfg = this.#ajaxConfig;
|
|
4480
|
+
|
|
4481
|
+
let payload;
|
|
4482
|
+
if (typeof cfg.dataByValues === "function") {
|
|
4483
|
+
payload = cfg.dataByValues(valuesArray);
|
|
4484
|
+
} else {
|
|
4485
|
+
payload = {
|
|
4486
|
+
values: valuesArray.join(","),
|
|
4487
|
+
load_by_values: "1",
|
|
4488
|
+
...(typeof cfg.data === "function" ? cfg.data("", 0) : (cfg.data || {}))
|
|
4489
|
+
};
|
|
4490
|
+
}
|
|
4491
|
+
|
|
4492
|
+
let response;
|
|
4493
|
+
if (cfg.method === "POST") {
|
|
4494
|
+
const formData = new URLSearchParams();
|
|
4495
|
+
Object.keys(payload).forEach(key => {
|
|
4496
|
+
formData.append(key, payload[key]);
|
|
4497
|
+
});
|
|
4498
|
+
|
|
4499
|
+
response = await fetch(cfg.url, {
|
|
4500
|
+
method: "POST",
|
|
4501
|
+
body: formData,
|
|
4502
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" }
|
|
4503
|
+
});
|
|
4504
|
+
} else {
|
|
4505
|
+
const params = new URLSearchParams(payload).toString();
|
|
4506
|
+
response = await fetch(`${cfg.url}?${params}`);
|
|
4507
|
+
}
|
|
4508
|
+
|
|
4509
|
+
if (!response.ok) {
|
|
4510
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
4511
|
+
}
|
|
4512
|
+
|
|
4513
|
+
const data = await response.json();
|
|
4514
|
+
const result = this.#parseResponse(data);
|
|
4515
|
+
|
|
4516
|
+
return {
|
|
4517
|
+
success: true,
|
|
4518
|
+
items: result.items
|
|
4519
|
+
};
|
|
4520
|
+
} catch (error) {
|
|
4521
|
+
console.error("Load by values error:", error);
|
|
4522
|
+
return {
|
|
4523
|
+
success: false,
|
|
4524
|
+
message: error.message,
|
|
4525
|
+
items: []
|
|
4526
|
+
};
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
|
|
4530
|
+
/**
|
|
4531
|
+
* Check if values exist in current options
|
|
4532
|
+
* @param {string[]} values - Values to check
|
|
4533
|
+
* @returns {{existing: string[], missing: string[]}}
|
|
4534
|
+
*/
|
|
4535
|
+
checkMissingValues(values) {
|
|
4536
|
+
const allOptions = Array.from(this.#select.options);
|
|
4537
|
+
const existingValues = allOptions.map(opt => opt.value);
|
|
4538
|
+
|
|
4539
|
+
const existing = values.filter(v => existingValues.includes(v));
|
|
4540
|
+
const missing = values.filter(v => !existingValues.includes(v));
|
|
4541
|
+
|
|
4542
|
+
return { existing, missing };
|
|
4543
|
+
}
|
|
4544
|
+
|
|
4187
4545
|
/**
|
|
4188
4546
|
* Configures AJAX settings used for remote searching and pagination.
|
|
4189
4547
|
*
|
|
@@ -4668,31 +5026,85 @@ class SelectObserver {
|
|
|
4668
5026
|
|
|
4669
5027
|
#debounceTimer = null;
|
|
4670
5028
|
|
|
5029
|
+
#lastSnapshot = null;
|
|
5030
|
+
|
|
5031
|
+
#DEBOUNCE_DELAY = 50;
|
|
5032
|
+
|
|
5033
|
+
|
|
4671
5034
|
/**
|
|
4672
|
-
*
|
|
4673
|
-
*
|
|
4674
|
-
*
|
|
5035
|
+
* Initializes the SelectObserver for a given <select> element.
|
|
5036
|
+
* Captures the initial snapshot, sets up a MutationObserver, and listens for custom "options:changed" events.
|
|
5037
|
+
* Changes are debounced to prevent excessive calls.
|
|
4675
5038
|
*
|
|
4676
|
-
* @param {HTMLSelectElement} select - The <select> element to
|
|
5039
|
+
* @param {HTMLSelectElement} select - The <select> element to observe.
|
|
4677
5040
|
*/
|
|
4678
5041
|
constructor(select) {
|
|
5042
|
+
this.#select = select;
|
|
5043
|
+
this.#lastSnapshot = this.#createSnapshot();
|
|
5044
|
+
|
|
4679
5045
|
this.#observer = new MutationObserver(() => {
|
|
4680
5046
|
clearTimeout(this.#debounceTimer);
|
|
4681
5047
|
this.#debounceTimer = setTimeout(() => {
|
|
4682
|
-
this
|
|
4683
|
-
},
|
|
5048
|
+
this.#handleChange();
|
|
5049
|
+
}, this.#DEBOUNCE_DELAY);
|
|
4684
5050
|
});
|
|
4685
5051
|
|
|
4686
|
-
this.#select = select;
|
|
4687
|
-
|
|
4688
5052
|
select.addEventListener("options:changed", () => {
|
|
4689
|
-
this
|
|
5053
|
+
clearTimeout(this.#debounceTimer);
|
|
5054
|
+
this.#debounceTimer = setTimeout(() => {
|
|
5055
|
+
this.#handleChange();
|
|
5056
|
+
}, this.#DEBOUNCE_DELAY);
|
|
4690
5057
|
});
|
|
4691
5058
|
}
|
|
4692
5059
|
|
|
4693
5060
|
/**
|
|
4694
|
-
*
|
|
4695
|
-
*
|
|
5061
|
+
* Creates a snapshot of the current state of the <select> element's options.
|
|
5062
|
+
* The snapshot includes option count, values, texts, and selected states for comparison.
|
|
5063
|
+
*
|
|
5064
|
+
* @returns {{length:number, values:string, texts:string, selected:string}} A snapshot of the options state.
|
|
5065
|
+
*/
|
|
5066
|
+
#createSnapshot() {
|
|
5067
|
+
const options = Array.from(this.#select.options);
|
|
5068
|
+
return {
|
|
5069
|
+
length: options.length,
|
|
5070
|
+
values: options.map(opt => opt.value).join(','),
|
|
5071
|
+
texts: options.map(opt => opt.text).join(','),
|
|
5072
|
+
selected: options.map(opt => opt.selected).join(',')
|
|
5073
|
+
};
|
|
5074
|
+
}
|
|
5075
|
+
|
|
5076
|
+
/**
|
|
5077
|
+
* Determines if there has been a real change in the <select> element's options or attributes.
|
|
5078
|
+
* Compares the new snapshot with the previous one and updates the stored snapshot if different.
|
|
5079
|
+
*
|
|
5080
|
+
* @returns {boolean} True if a real change occurred, otherwise false.
|
|
5081
|
+
*/
|
|
5082
|
+
#hasRealChange() {
|
|
5083
|
+
const newSnapshot = this.#createSnapshot();
|
|
5084
|
+
const changed = JSON.stringify(newSnapshot) !== JSON.stringify(this.#lastSnapshot);
|
|
5085
|
+
|
|
5086
|
+
if (changed) {
|
|
5087
|
+
this.#lastSnapshot = newSnapshot;
|
|
5088
|
+
}
|
|
5089
|
+
|
|
5090
|
+
return changed;
|
|
5091
|
+
}
|
|
5092
|
+
|
|
5093
|
+
/**
|
|
5094
|
+
* Handles detected changes after debouncing.
|
|
5095
|
+
* If a real change is found, invokes the onChanged() hook with the current <select> element.
|
|
5096
|
+
*/
|
|
5097
|
+
#handleChange() {
|
|
5098
|
+
if (!this.#hasRealChange()) {
|
|
5099
|
+
return;
|
|
5100
|
+
}
|
|
5101
|
+
|
|
5102
|
+
this.onChanged(this.#select);
|
|
5103
|
+
}
|
|
5104
|
+
|
|
5105
|
+
/**
|
|
5106
|
+
* Starts observing the <select> element for child list mutations and attribute changes.
|
|
5107
|
+
* Uses MutationObserver with a debounce mechanism to batch rapid updates.
|
|
4696
5108
|
*/
|
|
4697
5109
|
connect() {
|
|
4698
5110
|
this.#observer.observe(this.#select, {
|
|
@@ -4704,16 +5116,19 @@ class SelectObserver {
|
|
|
4704
5116
|
});
|
|
4705
5117
|
}
|
|
4706
5118
|
|
|
5119
|
+
|
|
4707
5120
|
/**
|
|
4708
|
-
* Hook
|
|
4709
|
-
* Override
|
|
5121
|
+
* Hook called when the <select> element's options or attributes change.
|
|
5122
|
+
* Override this method to implement custom update handling logic.
|
|
4710
5123
|
*
|
|
4711
|
-
* @param {HTMLSelectElement} options - The
|
|
5124
|
+
* @param {HTMLSelectElement} options - The current <select> element.
|
|
4712
5125
|
*/
|
|
4713
5126
|
onChanged(options) { }
|
|
4714
5127
|
|
|
5128
|
+
|
|
4715
5129
|
/**
|
|
4716
|
-
* Stops observing the select element and clears any pending debounce timers.
|
|
5130
|
+
* Stops observing the <select> element and clears any pending debounce timers.
|
|
5131
|
+
* Ensures no further change handling occurs after disconnecting.
|
|
4717
5132
|
*/
|
|
4718
5133
|
disconnect() {
|
|
4719
5134
|
clearTimeout(this.#debounceTimer);
|
|
@@ -4916,13 +5331,7 @@ class SelectBox {
|
|
|
4916
5331
|
tag: {
|
|
4917
5332
|
node: "div",
|
|
4918
5333
|
classList: "selective-ui-view",
|
|
4919
|
-
tabIndex: 0,
|
|
4920
|
-
role: "combobox",
|
|
4921
|
-
ariaExpanded: "false",
|
|
4922
|
-
ariaLabelledby: options.SEID_HOLDER,
|
|
4923
|
-
ariaControls: options.SEID_LIST,
|
|
4924
|
-
ariaHaspopup: "true",
|
|
4925
|
-
ariaMultiselectable: options.multiple ? "true" : "false",
|
|
5334
|
+
tabIndex: 0,
|
|
4926
5335
|
onkeydown: (e) => {
|
|
4927
5336
|
if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
|
|
4928
5337
|
e.preventDefault();
|
|
@@ -5242,10 +5651,21 @@ class SelectBox {
|
|
|
5242
5651
|
},
|
|
5243
5652
|
setValue(evtToken = null, value, trigger = true, force = false) {
|
|
5244
5653
|
!Array.isArray(value) && (value = [value]);
|
|
5654
|
+
|
|
5655
|
+
value = value.filter(v => v !== "" && v != null);
|
|
5656
|
+
|
|
5657
|
+
if (value.length === 0) {
|
|
5658
|
+
superThis.getModelOption().forEach(modelOption => {
|
|
5659
|
+
modelOption["selectedNonTrigger"] = false;
|
|
5660
|
+
});
|
|
5661
|
+
this.change(false, trigger);
|
|
5662
|
+
return;
|
|
5663
|
+
}
|
|
5245
5664
|
|
|
5246
5665
|
if (bindedOptions.multiple && bindedOptions.maxSelected > 0) {
|
|
5247
5666
|
if (value.length > bindedOptions.maxSelected) {
|
|
5248
|
-
|
|
5667
|
+
console.warn(`Cannot select more than ${bindedOptions.maxSelected} items`);
|
|
5668
|
+
return;
|
|
5249
5669
|
}
|
|
5250
5670
|
}
|
|
5251
5671
|
|
|
@@ -5253,12 +5673,58 @@ class SelectBox {
|
|
|
5253
5673
|
return;
|
|
5254
5674
|
}
|
|
5255
5675
|
|
|
5676
|
+
if (container.searchController?.isAjax()) {
|
|
5677
|
+
const { existing, missing } = container.searchController.checkMissingValues(value);
|
|
5678
|
+
|
|
5679
|
+
if (missing.length > 0) {
|
|
5680
|
+
console.log(`Loading ${missing.length} missing values from server...`);
|
|
5681
|
+
|
|
5682
|
+
(async () => {
|
|
5683
|
+
if (bindedOptions.loadingfield) {
|
|
5684
|
+
container.popup?.showLoading();
|
|
5685
|
+
}
|
|
5686
|
+
|
|
5687
|
+
try {
|
|
5688
|
+
const result = await container.searchController.loadByValues(missing);
|
|
5689
|
+
|
|
5690
|
+
if (result.success && result.items.length > 0) {
|
|
5691
|
+
result.items.forEach(item => {
|
|
5692
|
+
if (missing.includes(item.value)) {
|
|
5693
|
+
item.selected = true;
|
|
5694
|
+
}
|
|
5695
|
+
});
|
|
5696
|
+
|
|
5697
|
+
container.searchController['#applyAjaxResult'](
|
|
5698
|
+
result.items,
|
|
5699
|
+
true,
|
|
5700
|
+
true
|
|
5701
|
+
);
|
|
5702
|
+
|
|
5703
|
+
setTimeout(() => {
|
|
5704
|
+
superThis.getModelOption().forEach(modelOption => {
|
|
5705
|
+
modelOption["selectedNonTrigger"] = value.some(v => v == modelOption["value"]);
|
|
5706
|
+
});
|
|
5707
|
+
this.change(false, false);
|
|
5708
|
+
}, 100);
|
|
5709
|
+
} else if (missing.length > 0) {
|
|
5710
|
+
console.warn(`Could not load ${missing.length} values:`, missing);
|
|
5711
|
+
}
|
|
5712
|
+
} catch (error) {
|
|
5713
|
+
console.error("Error loading missing values:", error);
|
|
5714
|
+
} finally {
|
|
5715
|
+
if (bindedOptions.loadingfield) {
|
|
5716
|
+
container.popup?.hideLoading();
|
|
5717
|
+
}
|
|
5718
|
+
}
|
|
5719
|
+
})();
|
|
5720
|
+
}
|
|
5721
|
+
}
|
|
5722
|
+
|
|
5256
5723
|
if (trigger) {
|
|
5257
5724
|
const beforeChangeToken = iEvents.callEvent([this], ...bindedOptions.on.beforeChange);
|
|
5258
5725
|
if (beforeChangeToken.isCancel) {
|
|
5259
5726
|
return;
|
|
5260
5727
|
}
|
|
5261
|
-
|
|
5262
5728
|
superThis.oldValue = this.value;
|
|
5263
5729
|
}
|
|
5264
5730
|
|
|
@@ -5266,7 +5732,7 @@ class SelectBox {
|
|
|
5266
5732
|
modelOption["selectedNonTrigger"] = value.some(v => v == modelOption["value"]);
|
|
5267
5733
|
});
|
|
5268
5734
|
|
|
5269
|
-
if (!bindedOptions.multiple){
|
|
5735
|
+
if (!bindedOptions.multiple && value.length > 0) {
|
|
5270
5736
|
container.targetElement.value = value[0];
|
|
5271
5737
|
}
|
|
5272
5738
|
|
|
@@ -5318,8 +5784,14 @@ class SelectBox {
|
|
|
5318
5784
|
|
|
5319
5785
|
container.popup.open();
|
|
5320
5786
|
container.searchbox.show();
|
|
5321
|
-
|
|
5322
|
-
|
|
5787
|
+
const ViewPanel = /** @type {HTMLElement} */ (container.tags.ViewPanel);
|
|
5788
|
+
ViewPanel.setAttribute("aria-expanded", "true");
|
|
5789
|
+
ViewPanel.setAttribute("aria-controls", bindedOptions.SEID_LIST);
|
|
5790
|
+
ViewPanel.setAttribute("aria-haspopup", "listbox");
|
|
5791
|
+
ViewPanel.setAttribute("aria-labelledby", bindedOptions.SEID_HOLDER);
|
|
5792
|
+
if (bindedOptions.multiple) {
|
|
5793
|
+
ViewPanel.setAttribute("aria-multiselectable", "true");
|
|
5794
|
+
}
|
|
5323
5795
|
|
|
5324
5796
|
iEvents.callEvent([this], ...bindedOptions.on.show);
|
|
5325
5797
|
|
|
@@ -5987,7 +6459,7 @@ function markLoaded(name, version, api) {
|
|
|
5987
6459
|
console.log(`[${name}] v${version} loaded successfully`);
|
|
5988
6460
|
}
|
|
5989
6461
|
|
|
5990
|
-
const version = "1.0.
|
|
6462
|
+
const version = "1.0.5";
|
|
5991
6463
|
const name = "SelectiveUI";
|
|
5992
6464
|
|
|
5993
6465
|
const alreadyLoaded = checkDuplicate(name);
|