composite-select 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +5 -0
  2. package/commitlint.config.js +3 -0
  3. package/composition/composite-select/CompositeManager.js +43 -0
  4. package/composition/composite-select/composite-select.js +199 -0
  5. package/composition/composite-select/debounce.js +10 -0
  6. package/composition/composite-select/helpers.js +96 -0
  7. package/composition/composite-select/react.js +189 -0
  8. package/composition/container/ContainerManager.js +76 -0
  9. package/composition/img/ai.png +0 -0
  10. package/composition/img/chatgpt.png +0 -0
  11. package/composition/img/claude.png +0 -0
  12. package/composition/img/gemini.png +0 -0
  13. package/composition/img/gmail.png +0 -0
  14. package/composition/img/google_calendar.png +0 -0
  15. package/composition/img/google_drive.png +0 -0
  16. package/composition/img/google_keep.png +0 -0
  17. package/composition/img/img.json +5 -0
  18. package/composition/img/perplexity.png +0 -0
  19. package/composition/img/t3chat.png +0 -0
  20. package/composition/img/timeanddate.png +0 -0
  21. package/composition/img/tools.png +0 -0
  22. package/composition/img/youtube.png +0 -0
  23. package/composition/options-section/OptionsSectionManager.css +263 -0
  24. package/composition/options-section/OptionsSectionManager.js +486 -0
  25. package/composition/options-section/options-section.js +245 -0
  26. package/composition/options-section/react.js +90 -0
  27. package/composition/selected-section/SelectedSectionManager.css +214 -0
  28. package/composition/selected-section/SelectedSectionManager.js +336 -0
  29. package/composition/selected-section/react.js +91 -0
  30. package/composition/selected-section/selected-section.js +207 -0
  31. package/composition/unbind/clickOutside.js +17 -0
  32. package/diff/coreBundle.patch +13 -0
  33. package/diff/recorderApp.patch +13 -0
  34. package/dist/cjs/Module.cjs +15 -0
  35. package/dist/cjs/composite-select/CompositeManager.js +43 -0
  36. package/dist/cjs/composite-select/composite-select.js +199 -0
  37. package/dist/cjs/composite-select/debounce.js +10 -0
  38. package/dist/cjs/composite-select/helpers.js +96 -0
  39. package/dist/cjs/composite-select/react.js +189 -0
  40. package/dist/cjs/container/ContainerManager.js +76 -0
  41. package/dist/cjs/createSubscriber.cjs +48 -0
  42. package/dist/cjs/options-section/OptionsSectionManager.js +486 -0
  43. package/dist/cjs/options-section/options-section.js +245 -0
  44. package/dist/cjs/options-section/react.js +90 -0
  45. package/dist/cjs/selected-section/SelectedSectionManager.js +336 -0
  46. package/dist/cjs/selected-section/react.js +91 -0
  47. package/dist/cjs/selected-section/selected-section.js +207 -0
  48. package/dist/cjs/types.cjs +1 -0
  49. package/dist/cjs/unbind/clickOutside.js +17 -0
  50. package/dist/esm/Module.js +15 -0
  51. package/dist/esm/composite-select/CompositeManager.js +43 -0
  52. package/dist/esm/composite-select/composite-select.js +199 -0
  53. package/dist/esm/composite-select/debounce.js +10 -0
  54. package/dist/esm/composite-select/helpers.js +96 -0
  55. package/dist/esm/composite-select/react.js +189 -0
  56. package/dist/esm/container/ContainerManager.js +76 -0
  57. package/dist/esm/createSubscriber.js +48 -0
  58. package/dist/esm/options-section/OptionsSectionManager.js +486 -0
  59. package/dist/esm/options-section/options-section.js +245 -0
  60. package/dist/esm/options-section/react.js +90 -0
  61. package/dist/esm/selected-section/SelectedSectionManager.js +336 -0
  62. package/dist/esm/selected-section/react.js +91 -0
  63. package/dist/esm/selected-section/selected-section.js +207 -0
  64. package/dist/esm/types.js +1 -0
  65. package/dist/esm/unbind/clickOutside.js +17 -0
  66. package/dist/types/Module.d.ts +15 -0
  67. package/dist/types/composite-select/CompositeManager.d.ts +21 -0
  68. package/dist/types/composite-select/ContainerManager.html.d.ts +1 -0
  69. package/dist/types/composite-select/composite-select.d.ts +26 -0
  70. package/dist/types/composite-select/composite-select.html.d.ts +1 -0
  71. package/dist/types/composite-select/debounce.d.ts +1 -0
  72. package/dist/types/composite-select/helpers.d.ts +38 -0
  73. package/dist/types/composite-select/namesSource.d.ts +4 -0
  74. package/dist/types/composite-select/react.d.ts +61 -0
  75. package/dist/types/composite-select/urlManager.d.ts +49 -0
  76. package/dist/types/composite-select/urlManagerWc.d.ts +44 -0
  77. package/dist/types/container/ContainerManager.d.ts +33 -0
  78. package/dist/types/createSubscriber.d.ts +26 -0
  79. package/dist/types/options-section/OptionsSectionManager.d.ts +117 -0
  80. package/dist/types/options-section/OptionsSectionManager.html.d.ts +1 -0
  81. package/dist/types/options-section/OptionsSectionManagerWebComponent.attributes.html.d.ts +1 -0
  82. package/dist/types/options-section/OptionsSectionManagerWebComponent.html.d.ts +1 -0
  83. package/dist/types/options-section/OptionsSectionManagerWebComponent.nocssrequest.html.d.ts +1 -0
  84. package/dist/types/options-section/options-section.d.ts +67 -0
  85. package/dist/types/options-section/react.d.ts +27 -0
  86. package/dist/types/options-section/urlManager.d.ts +28 -0
  87. package/dist/types/selected-section/SelectedSectionManager.d.ts +89 -0
  88. package/dist/types/selected-section/SelectedSectionManager.html.d.ts +1 -0
  89. package/dist/types/selected-section/SelectedSectionManager.templates.html.d.ts +1 -0
  90. package/dist/types/selected-section/SelectedSectionManagerWebComponent.attributes.html.d.ts +1 -0
  91. package/dist/types/selected-section/SelectedSectionManagerWebComponent.html.d.ts +1 -0
  92. package/dist/types/selected-section/SelectedSectionManagerWebComponent.nocssrequest.html.d.ts +1 -0
  93. package/dist/types/selected-section/react.d.ts +32 -0
  94. package/dist/types/selected-section/selected-section.d.ts +54 -0
  95. package/dist/types/selected-section/urlManager.d.ts +25 -0
  96. package/dist/types/types.d.ts +9 -0
  97. package/dist/types/unbind/clickOutside.d.ts +1 -0
  98. package/floating-label-pattern.css +502 -0
  99. package/js/CenterAndHeightResizer.js +263 -0
  100. package/js/CenterResizer.js +190 -0
  101. package/madooei.tar.gz +0 -0
  102. package/package.json +28 -0
  103. package/release.config.js +3 -0
  104. package/test/lib.d.ts +6 -0
  105. package/test/lib.js +30 -0
@@ -0,0 +1,43 @@
1
+ import { ContainerManager } from "../container/ContainerManager.js";
2
+ import { OptionsSectionManager } from "../options-section/OptionsSectionManager.js";
3
+ import { SelectedSectionManager } from "../selected-section/SelectedSectionManager.js";
4
+ import { clickOutside } from "../unbind/clickOutside.js";
5
+ export class CompositeManager {
6
+ container;
7
+ selected;
8
+ options;
9
+ _unbindClickOutside;
10
+ _parent;
11
+ constructor(parent, options = {}) {
12
+ this._parent = parent;
13
+ this.container = new ContainerManager(this._parent, options.container);
14
+ {
15
+ const target = this.container.getTarget();
16
+ const div = document.createElement("div");
17
+ target.appendChild(div);
18
+ this.selected = new SelectedSectionManager(div, options.select);
19
+ this.selected.getSubscriber().bind("onFocus", (e) => {
20
+ this.container.show();
21
+ this.options.setFocus();
22
+ });
23
+ }
24
+ {
25
+ const popover = this.container.getPopover();
26
+ const div = document.createElement("div");
27
+ popover.appendChild(div);
28
+ this.options = new OptionsSectionManager(div, options.options);
29
+ }
30
+ this._unbindClickOutside = clickOutside([this.container.getParent()], () => {
31
+ this.container.hide();
32
+ });
33
+ }
34
+ destroy() {
35
+ this._unbindClickOutside();
36
+ this.options.destroy();
37
+ if (typeof this.selected.destroy === "function") {
38
+ this.selected.destroy();
39
+ }
40
+ this.container.destroy();
41
+ this._parent.innerHTML = "";
42
+ }
43
+ }
@@ -0,0 +1,199 @@
1
+ import { CompositeManager } from "./CompositeManager.js";
2
+ /**
3
+ * CompositeSelect web component — wraps CompositeManager.
4
+ *
5
+ * Does NOT use shadow DOM because ContainerManager creates a <div popover>
6
+ * whose positioning CSS classes (cover-bottom, bottom, etc. from popover.css)
7
+ * are global and cannot penetrate shadow DOM boundaries.
8
+ * The host page must load the required CSS via <link> tags, exactly as ContainerManager.html does.
9
+ *
10
+ * Attribute prefixes:
11
+ * selected-* → SelectedSectionManager properties / events
12
+ * options-* → OptionsSectionManager properties / events
13
+ * container-* → ContainerManager properties / events
14
+ */
15
+ export class CompositeSelect extends HTMLElement {
16
+ _manager = null;
17
+ _mountPoint;
18
+ static get observedAttributes() {
19
+ return [
20
+ // selected-* attributes
21
+ "selected-selected",
22
+ "selected-show-input",
23
+ "selected-value",
24
+ "selected-label",
25
+ "selected-disabled",
26
+ "selected-error",
27
+ "selected-loading",
28
+ "selected-show-delete",
29
+ // options-* attributes
30
+ "options-options",
31
+ "options-loading",
32
+ "options-value",
33
+ "options-label",
34
+ "options-disabled",
35
+ "options-max-height",
36
+ "options-show-footer",
37
+ "options-show-filter",
38
+ // container-* attributes
39
+ "container-position",
40
+ "container-offset",
41
+ ];
42
+ }
43
+ constructor() {
44
+ super();
45
+ // Light DOM — no shadow DOM (popover CSS must be global)
46
+ this._mountPoint = document.createElement("div");
47
+ }
48
+ connectedCallback() {
49
+ if (!this._mountPoint.parentNode) {
50
+ this.appendChild(this._mountPoint);
51
+ }
52
+ if (this._manager)
53
+ return;
54
+ const hasBoolAttr = (name, defaultVal) => this.hasAttribute(name) ? this.getAttribute(name) !== "false" : defaultVal;
55
+ // ── DEBUG: dump all boolean attrs at init time ────────────────────────────
56
+ const _bools = {
57
+ "selected-disabled": { hasAttr: this.hasAttribute("selected-disabled"), attrVal: this.getAttribute("selected-disabled"), resolved: hasBoolAttr("selected-disabled", false) },
58
+ "selected-error": { hasAttr: this.hasAttribute("selected-error"), attrVal: this.getAttribute("selected-error"), resolved: hasBoolAttr("selected-error", false) },
59
+ "selected-loading": { hasAttr: this.hasAttribute("selected-loading"), attrVal: this.getAttribute("selected-loading"), resolved: hasBoolAttr("selected-loading", false) },
60
+ "selected-show-input": { hasAttr: this.hasAttribute("selected-show-input"), attrVal: this.getAttribute("selected-show-input"), resolved: hasBoolAttr("selected-show-input", true) },
61
+ "selected-show-delete": { hasAttr: this.hasAttribute("selected-show-delete"), attrVal: this.getAttribute("selected-show-delete"), resolved: hasBoolAttr("selected-show-delete", true) },
62
+ "options-loading": { hasAttr: this.hasAttribute("options-loading"), attrVal: this.getAttribute("options-loading"), resolved: hasBoolAttr("options-loading", false) },
63
+ "options-disabled": { hasAttr: this.hasAttribute("options-disabled"), attrVal: this.getAttribute("options-disabled"), resolved: hasBoolAttr("options-disabled", false) },
64
+ "options-show-footer": { hasAttr: this.hasAttribute("options-show-footer"), attrVal: this.getAttribute("options-show-footer"), resolved: hasBoolAttr("options-show-footer", true) },
65
+ "options-show-filter": { hasAttr: this.hasAttribute("options-show-filter"), attrVal: this.getAttribute("options-show-filter"), resolved: hasBoolAttr("options-show-filter", true) },
66
+ };
67
+ for (const [attr, info] of Object.entries(_bools)) {
68
+ console.log(`[composite-select][connectedCallback] ${attr}: hasAttr=type >${typeof info.hasAttr}< value >${info.hasAttr}< attrVal=type >${typeof info.attrVal}< value >${info.attrVal}< resolved=type >${typeof info.resolved}< value >${info.resolved}<`);
69
+ }
70
+ // ─────────────────────────────────────────────────────────────────────────
71
+ this._manager = new CompositeManager(this._mountPoint, {
72
+ select: {
73
+ selected: this._parseJSON(this.getAttribute("selected-selected")) ?? [],
74
+ showInput: hasBoolAttr("selected-show-input", true),
75
+ value: this.getAttribute("selected-value") || "",
76
+ label: this.getAttribute("selected-label") || "",
77
+ disabled: hasBoolAttr("selected-disabled", false),
78
+ error: hasBoolAttr("selected-error", false),
79
+ loading: hasBoolAttr("selected-loading", false),
80
+ showDelete: hasBoolAttr("selected-show-delete", true),
81
+ },
82
+ options: {
83
+ options: this._parseJSON(this.getAttribute("options-options")) ?? [],
84
+ loading: hasBoolAttr("options-loading", false),
85
+ value: this.getAttribute("options-value") || "",
86
+ label: this.getAttribute("options-label") || "",
87
+ disabled: hasBoolAttr("options-disabled", false),
88
+ maxHeight: this.getAttribute("options-max-height") || undefined,
89
+ showFooter: hasBoolAttr("options-show-footer", true),
90
+ showFilter: hasBoolAttr("options-show-filter", true),
91
+ },
92
+ container: {},
93
+ });
94
+ if (this.getAttribute("container-position")) {
95
+ this._manager.container.setPosition(this.getAttribute("container-position"));
96
+ }
97
+ if (this.getAttribute("container-offset")) {
98
+ this._manager.container.setOffset(this.getAttribute("container-offset"));
99
+ }
100
+ }
101
+ disconnectedCallback() {
102
+ this._manager?.destroy();
103
+ this._manager = null;
104
+ }
105
+ attributeChangedCallback(name, _oldValue, newValue) {
106
+ if (!this._manager)
107
+ return;
108
+ const isTrue = newValue !== null && newValue !== "false";
109
+ // ── DEBUG: dump bool attrs as they change ─────────────────────────────────
110
+ const _boolAttrs = new Set(["selected-disabled", "selected-error", "selected-loading", "selected-show-input", "selected-show-delete", "options-loading", "options-disabled", "options-show-footer", "options-show-filter"]);
111
+ if (_boolAttrs.has(name)) {
112
+ console.log(`[composite-select][attributeChangedCallback] ${name}: isTrue=type >${typeof isTrue}< value >${isTrue}< newValue=type >${typeof newValue}< value >${newValue}< oldValue=type >${typeof _oldValue}< value >${_oldValue}<`);
113
+ }
114
+ // ─────────────────────────────────────────────────────────────────────────
115
+ switch (name) {
116
+ // ── selected ─────────────────────────────────────────────────────────
117
+ case "selected-selected": {
118
+ const parsed = this._parseJSON(newValue);
119
+ if (parsed !== undefined) {
120
+ this._manager.selected.setSelected(parsed);
121
+ }
122
+ break;
123
+ }
124
+ case "selected-show-input":
125
+ this._manager.selected.setShowInput(isTrue);
126
+ break;
127
+ case "selected-value":
128
+ this._manager.selected.setValue(newValue);
129
+ break;
130
+ case "selected-label":
131
+ this._manager.selected.setLabel(newValue);
132
+ break;
133
+ case "selected-disabled":
134
+ this._manager.selected.setDisabled(isTrue);
135
+ break;
136
+ case "selected-error":
137
+ this._manager.selected.setError(isTrue);
138
+ break;
139
+ case "selected-loading":
140
+ this._manager.selected.setLoading(isTrue);
141
+ break;
142
+ case "selected-show-delete":
143
+ this._manager.selected.setShowDelete(isTrue);
144
+ break;
145
+ // ── options ──────────────────────────────────────────────────────────
146
+ case "options-options": {
147
+ const parsed = this._parseJSON(newValue);
148
+ if (parsed !== undefined) {
149
+ this._manager.options.setOptions(parsed);
150
+ }
151
+ break;
152
+ }
153
+ case "options-loading":
154
+ this._manager.options.setLoading(isTrue);
155
+ break;
156
+ case "options-value":
157
+ this._manager.options.setValue(newValue);
158
+ break;
159
+ case "options-label":
160
+ this._manager.options.setLabel(newValue);
161
+ break;
162
+ case "options-disabled":
163
+ this._manager.options.setDisabled(isTrue);
164
+ break;
165
+ case "options-max-height":
166
+ this._manager.options.setMaxHeight(newValue);
167
+ break;
168
+ case "options-show-footer":
169
+ this._manager.options.setShowFooter(isTrue);
170
+ break;
171
+ case "options-show-filter":
172
+ this._manager.options.setShowFilter(isTrue);
173
+ break;
174
+ // ── container ────────────────────────────────────────────────────────
175
+ case "container-position":
176
+ this._manager.container.setPosition(newValue);
177
+ break;
178
+ case "container-offset":
179
+ this._manager.container.setOffset(newValue);
180
+ break;
181
+ }
182
+ }
183
+ // ─── Accessor ─────────────────────────────────────────────────────────────
184
+ getManager() {
185
+ return this._manager;
186
+ }
187
+ _parseJSON(val) {
188
+ if (!val)
189
+ return undefined;
190
+ try {
191
+ return JSON.parse(val);
192
+ }
193
+ catch (e) {
194
+ console.error(`CompositeSelect: failed to parse JSON:`, val, e);
195
+ return undefined;
196
+ }
197
+ }
198
+ }
199
+ customElements.define("composite-select", CompositeSelect);
@@ -0,0 +1,10 @@
1
+ export default function debounce(fn, delay) {
2
+ var timer = null;
3
+ return function (...args) {
4
+ var context = this;
5
+ clearTimeout(timer);
6
+ timer = setTimeout(function () {
7
+ fn.apply(context, args);
8
+ }, delay);
9
+ };
10
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Toggles an item in the selected list: adds if missing, removes if present.
3
+ */
4
+ export function togglePresenceOnTheList(selected, item) {
5
+ const tmp = [...selected];
6
+ const newList = tmp.filter((i) => String(i.id) !== String(item.id));
7
+ if (newList.length === tmp.length) {
8
+ tmp.push(item);
9
+ return tmp;
10
+ }
11
+ return newList;
12
+ }
13
+ /**
14
+ * Resolves a list of IDs into full option objects and merges them into the current selected list (seed).
15
+ * This function is designed to preserve any existing items in the selected list (including "extra" items
16
+ * that might not exist in the current options array) while ensuring the final result is unique and deduplicated by ID.
17
+ */
18
+ export function selectedFindDeduplicatedInOptionsByIds(options, ids, seed) {
19
+ let tmp = [];
20
+ if (seed) {
21
+ tmp = [...seed];
22
+ }
23
+ ids.forEach((id) => {
24
+ const found = tmp.some((i) => String(i.id) === String(id));
25
+ if (!found) {
26
+ tmp.push(options.find((o) => String(o.id) === String(id)));
27
+ }
28
+ });
29
+ tmp = deduplicateArrayById(tmp);
30
+ return tmp;
31
+ }
32
+ /**
33
+ * Updates the 'selected' flag on each option based on whether its ID exists in the provided selected list.
34
+ */
35
+ export function markSelectedByIds(options, selectedIds) {
36
+ if (selectedIds.length === 0) {
37
+ return [...options];
38
+ }
39
+ return options.map((option) => {
40
+ const opt = { ...option };
41
+ opt.selected = selectedIds.includes(option.id);
42
+ return opt;
43
+ });
44
+ }
45
+ /**
46
+ * Returns a new array with duplicate items removed based on their 'id' property.
47
+ * Interesting trick,
48
+ * if you know you will have more than one instance of object by the same id then pass these which you want to keep first.
49
+ * It is usefull in certain circumstances:
50
+ * For example when onFocus is fired we can unselect objects from selected by placing
51
+ * [...options, ...selected] in this order
52
+ * then unselected options will win over selected
53
+ * and then after that we can filter just these which are selected still and update manager.selected.setSelected()
54
+ * this way we will unselect
55
+ */
56
+ export function deduplicateArrayById(arr) {
57
+ return arr.filter((item, index) => arr.findIndex((i) => String(i.id) === String(item.id)) === index);
58
+ }
59
+ /**
60
+ * Sorts a list of elements by their ID string value.
61
+ */
62
+ export function sortById(list, asc = true) {
63
+ return [...list].sort((a, b) => {
64
+ const aId = String(a.id);
65
+ const bId = String(b.id);
66
+ if (aId < bId) {
67
+ return asc ? -1 : 1;
68
+ }
69
+ if (aId > bId) {
70
+ return asc ? 1 : -1;
71
+ }
72
+ return 0;
73
+ });
74
+ }
75
+ /**
76
+ * Wraps matches of search terms in the provided text with a highlight span.
77
+ * Supports multi-word search and case-insensitive matching.
78
+ */
79
+ export function markSearchWithSpan(text, search) {
80
+ if (!search || !search.trim()) {
81
+ return text;
82
+ }
83
+ // Split by whitespace, deduplicate, and remove empty strings
84
+ const words = Array.from(new Set(search.trim().split(/\s+/).filter(Boolean)));
85
+ if (words.length === 0) {
86
+ return text;
87
+ }
88
+ // Escape special regex characters in each word
89
+ const escapedWords = words.map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
90
+ // Sort by length descending to match longest words first (greedy matching)
91
+ escapedWords.sort((a, b) => b.length - a.length);
92
+ // Create a regex to match any of the words, case-insensitively, globally
93
+ const regex = new RegExp(`(${escapedWords.join("|")})`, "gi");
94
+ // Replace matches with the highlighted span
95
+ return text.replace(regex, '<span class="highlight">$1</span>');
96
+ }
@@ -0,0 +1,189 @@
1
+ // @ts-ignore
2
+ import React from "react";
3
+ import "./composite-select.js";
4
+ export const CompositeSelect = React.forwardRef((props, ref) => {
5
+ const { "selected-selected": selectedSelected, "selected-show-input": selectedShowInput, "selected-value": selectedValue, "selected-label": selectedLabel, "selected-disabled": selectedDisabled, "selected-error": selectedError, "selected-loading": selectedLoading, "selected-show-delete": selectedShowDelete, "selected-onDelete": selectedOnDelete, "selected-onClear": selectedOnClear, "selected-onInputChange": selectedOnChangeValue, "selected-onFocus": selectedOnFocus, "selected-onChange": selectedOnChange, "selected-onComponentChange": selectedOnSelectedItemsChanged, "options-options": optionsOptions, "options-loading": optionsLoading, "options-value": optionsValue, "options-label": optionsLabel, "options-disabled": optionsDisabled, "options-max-height": optionsMaxHeight, "options-show-footer": optionsShowFooter, "options-show-filter": optionsFilter, "options-onItemPick": optionsOnItemPick, "options-onInputChange": optionsOnInputChange, "options-onCancel": optionsOnCancel, "options-onOk": optionsOnOk, "options-onHighlightChange": optionsOnHighlightChange, "options-onClear": optionsOnClear, "options-onComponentChange": optionsOnOptionsChanged, "container-position": containerPosition, "container-offset": containerOffset, "container-onClose": containerOnClose, children, ...rest } = props;
6
+ const internalRef = React.useRef(null);
7
+ const setRef = React.useCallback((node) => {
8
+ internalRef.current = node;
9
+ if (typeof ref === "function") {
10
+ ref(node);
11
+ }
12
+ else if (ref) {
13
+ ref.current = node;
14
+ }
15
+ }, [ref]);
16
+ React.useLayoutEffect(() => {
17
+ const el = internalRef.current;
18
+ if (el && el.getManager && el.getManager()) {
19
+ const mgr = el.getManager();
20
+ const unbinds = [];
21
+ // SelectedSection events
22
+ const selSub = mgr.selected.getSubscriber();
23
+ if (selSub) {
24
+ if (selectedOnDelete) {
25
+ unbinds.push(selSub.bind("onDelete", (id) => selectedOnDelete(id)));
26
+ }
27
+ if (selectedOnClear) {
28
+ unbinds.push(selSub.bind("onClear", () => selectedOnClear()));
29
+ }
30
+ if (selectedOnChangeValue) {
31
+ unbinds.push(selSub.bind("onInputChange", (e, previousValue) => selectedOnChangeValue({
32
+ originalEvent: e,
33
+ value: e.target.value,
34
+ key: e.key || "",
35
+ previousValue,
36
+ })));
37
+ }
38
+ if (selectedOnFocus) {
39
+ unbinds.push(selSub.bind("onFocus", (e) => selectedOnFocus({ originalEvent: e })));
40
+ }
41
+ if (selectedOnChange) {
42
+ unbinds.push(selSub.bind("onChange", (selected) => selectedOnChange(selected)));
43
+ }
44
+ if (selectedOnSelectedItemsChanged) {
45
+ unbinds.push(selSub.bind("onComponentChange", (options, reason) => selectedOnSelectedItemsChanged(options, reason)));
46
+ }
47
+ }
48
+ // OptionsSection events
49
+ const optSub = mgr.options.getSubscriber();
50
+ if (optSub) {
51
+ if (optionsOnItemPick) {
52
+ unbinds.push(optSub.bind("onItemPick", (item) => optionsOnItemPick(item)));
53
+ }
54
+ if (optionsOnInputChange) {
55
+ unbinds.push(optSub.bind("onInputChange", (e, previousValue) => optionsOnInputChange({
56
+ originalEvent: e,
57
+ value: e.target.value,
58
+ previousValue,
59
+ })));
60
+ }
61
+ if (optionsOnCancel) {
62
+ unbinds.push(optSub.bind("onCancel", () => optionsOnCancel()));
63
+ }
64
+ if (optionsOnOk) {
65
+ unbinds.push(optSub.bind("onOk", () => optionsOnOk()));
66
+ }
67
+ if (optionsOnHighlightChange) {
68
+ unbinds.push(optSub.bind("onHighlightChange", (id) => optionsOnHighlightChange(id)));
69
+ }
70
+ if (optionsOnClear) {
71
+ unbinds.push(optSub.bind("onClear", () => optionsOnClear()));
72
+ }
73
+ if (optionsOnOptionsChanged) {
74
+ unbinds.push(optSub.bind("onComponentChange", (options, reason) => optionsOnOptionsChanged(options, reason)));
75
+ }
76
+ }
77
+ // Container events
78
+ const conSub = mgr.container.getSubscriber();
79
+ if (conSub) {
80
+ if (containerOnClose) {
81
+ unbinds.push(conSub.bind("onClose", () => containerOnClose()));
82
+ }
83
+ }
84
+ return () => {
85
+ unbinds.forEach((u) => u());
86
+ };
87
+ }
88
+ }, [
89
+ selectedOnDelete,
90
+ selectedOnClear,
91
+ selectedOnChangeValue,
92
+ selectedOnFocus,
93
+ selectedOnChange,
94
+ selectedOnSelectedItemsChanged,
95
+ optionsOnItemPick,
96
+ optionsOnInputChange,
97
+ optionsOnCancel,
98
+ optionsOnOk,
99
+ optionsOnHighlightChange,
100
+ optionsOnClear,
101
+ optionsOnOptionsChanged,
102
+ containerOnClose,
103
+ ]);
104
+ React.useLayoutEffect(() => {
105
+ const el = internalRef.current;
106
+ if (el && el.getManager && el.getManager()) {
107
+ if (selectedSelected !== undefined) {
108
+ el.getManager().selected.setSelected(typeof selectedSelected === "string" ? JSON.parse(selectedSelected) : selectedSelected);
109
+ }
110
+ if (optionsOptions !== undefined) {
111
+ el.getManager().options.setOptions(typeof optionsOptions === "string" ? JSON.parse(optionsOptions) : optionsOptions);
112
+ }
113
+ }
114
+ }, [selectedSelected, optionsOptions]);
115
+ const wcProps = { ...rest, ref: setRef };
116
+ // Map selected-* attributes
117
+ if (String(selectedShowInput) === "true") {
118
+ wcProps["selected-show-input"] = "true";
119
+ }
120
+ else {
121
+ delete wcProps["selected-show-input"];
122
+ }
123
+ if (selectedValue !== undefined)
124
+ wcProps["selected-value"] = selectedValue;
125
+ if (selectedLabel !== undefined)
126
+ wcProps["selected-label"] = selectedLabel;
127
+ if (String(selectedDisabled) === "true") {
128
+ wcProps["selected-disabled"] = "true";
129
+ }
130
+ else {
131
+ delete wcProps["selected-disabled"];
132
+ }
133
+ if (String(selectedError) === "true") {
134
+ wcProps["selected-error"] = "true";
135
+ }
136
+ else {
137
+ delete wcProps["selected-error"];
138
+ }
139
+ if (String(selectedLoading) === "true") {
140
+ wcProps["selected-loading"] = "true";
141
+ }
142
+ else {
143
+ delete wcProps["selected-loading"];
144
+ }
145
+ if (String(selectedShowDelete) === "true") {
146
+ wcProps["selected-show-delete"] = "true";
147
+ }
148
+ else {
149
+ delete wcProps["selected-show-delete"];
150
+ }
151
+ // Map options-* attributes
152
+ if (String(optionsLoading) === "true") {
153
+ wcProps["options-loading"] = "true";
154
+ }
155
+ else {
156
+ delete wcProps["options-loading"];
157
+ }
158
+ if (optionsValue !== undefined)
159
+ wcProps["options-value"] = optionsValue;
160
+ if (optionsLabel !== undefined)
161
+ wcProps["options-label"] = optionsLabel;
162
+ if (String(optionsDisabled) === "true") {
163
+ wcProps["options-disabled"] = "true";
164
+ }
165
+ else {
166
+ delete wcProps["options-disabled"];
167
+ }
168
+ if (optionsMaxHeight !== undefined)
169
+ wcProps["options-max-height"] = optionsMaxHeight;
170
+ if (String(optionsShowFooter) === "true") {
171
+ wcProps["options-show-footer"] = "true";
172
+ }
173
+ else {
174
+ delete wcProps["options-show-footer"];
175
+ }
176
+ if (String(optionsFilter) === "true") {
177
+ wcProps["options-show-filter"] = "true";
178
+ }
179
+ else {
180
+ delete wcProps["options-show-filter"];
181
+ }
182
+ // Map container-* attributes
183
+ if (containerPosition !== undefined)
184
+ wcProps["container-position"] = containerPosition;
185
+ if (containerOffset !== undefined)
186
+ wcProps["container-offset"] = containerOffset;
187
+ return React.createElement("composite-select", wcProps, children);
188
+ });
189
+ export default CompositeSelect;
@@ -0,0 +1,76 @@
1
+ import createSubscriber from "../createSubscriber.js";
2
+ export class ContainerManager {
3
+ parent;
4
+ target;
5
+ popover;
6
+ currentPosition = "cover-bottom";
7
+ propOptions;
8
+ _subscriber = createSubscriber();
9
+ constructor(parent, options = {}) {
10
+ this.parent = parent;
11
+ this.propOptions = {
12
+ ...options,
13
+ };
14
+ this.target = document.createElement("div");
15
+ this.popover = document.createElement("div");
16
+ this.popover.setAttribute("popover", "manual");
17
+ this.popover.setAttribute("data-popover", "");
18
+ this.popover.style.width = "anchor-size(width)";
19
+ this.popover.style.boxSizing = "border-box";
20
+ this.popover.style.border = "none";
21
+ this.setPosition(this.currentPosition);
22
+ this.setOffset("0px");
23
+ this.parent.appendChild(this.target);
24
+ this.parent.appendChild(this.popover);
25
+ this.popover.addEventListener("beforetoggle", (event) => {
26
+ if (event.newState === "closed") {
27
+ if (this.propOptions.onClose) {
28
+ this.propOptions.onClose();
29
+ }
30
+ this._subscriber.trigger("onClose");
31
+ }
32
+ });
33
+ }
34
+ getSubscriber() {
35
+ return this._subscriber;
36
+ }
37
+ show() {
38
+ this.popover.showPopover({ source: this.target });
39
+ }
40
+ hide() {
41
+ this.popover.hidePopover();
42
+ }
43
+ getParent() {
44
+ return this.parent;
45
+ }
46
+ getTarget() {
47
+ return this.target;
48
+ }
49
+ getPopover() {
50
+ return this.popover;
51
+ }
52
+ getPosition() {
53
+ return this.currentPosition;
54
+ }
55
+ setPosition(position) {
56
+ if (this.currentPosition) {
57
+ this.popover.classList.remove(this.currentPosition);
58
+ }
59
+ this.currentPosition = position;
60
+ if (this.currentPosition) {
61
+ this.popover.classList.add(this.currentPosition);
62
+ }
63
+ }
64
+ setOffset(offset) {
65
+ this.popover.style.setProperty("--popover-offset", offset);
66
+ }
67
+ destroy() {
68
+ if (this.target && this.target.parentNode) {
69
+ this.target.parentNode.removeChild(this.target);
70
+ }
71
+ if (this.popover && this.popover.parentNode) {
72
+ this.popover.parentNode.removeChild(this.popover);
73
+ }
74
+ this._subscriber.destroy();
75
+ }
76
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ type Events = {
3
+ login: [user: { id: string; name: string }];
4
+ logout: [];
5
+ };
6
+
7
+ const subscriber = createSubscriber<Events>();
8
+
9
+ const loginHandler = () => {}
10
+ const logoutHandler = () => {}
11
+
12
+ subscriber.bind("login", loginHandler);
13
+ subscriber.bind("logout", logoutHandler);
14
+
15
+ subscriber.trigger("login", { id: "1", name: "John" });
16
+
17
+ subscriber.trigger("logout");
18
+ */
19
+ export default function createSubscriber() {
20
+ const bindings = new Map();
21
+ function bind(event, handler) {
22
+ if (!bindings.has(event)) {
23
+ bindings.set(event, new Set());
24
+ }
25
+ bindings.get(event).add(handler);
26
+ return () => unbind(event, handler);
27
+ }
28
+ function unbind(event, handler) {
29
+ bindings.get(event)?.delete(handler);
30
+ }
31
+ function unbindGroup(event) {
32
+ bindings.delete(event);
33
+ }
34
+ function trigger(event, ...args) {
35
+ bindings.get(event)?.forEach((handler) => {
36
+ handler(...args);
37
+ });
38
+ }
39
+ function destroy() {
40
+ bindings.clear();
41
+ }
42
+ function getCount() {
43
+ let count = 0;
44
+ bindings.forEach((handlers) => (count += handlers.size));
45
+ return count;
46
+ }
47
+ return { bind, unbind, unbindGroup, trigger, destroy, getCount };
48
+ }