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,91 @@
1
+ // @ts-ignore
2
+ import React from "react";
3
+ import "./selected-section.js";
4
+ export const SelectedSection = React.forwardRef((props, ref) => {
5
+ const { label, "show-input": showInput, value, disabled, error, loading, selected: selectedSelected, children, onDelete, onClear, onChange, onInputChange, onFocus, ...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 && selectedSelected !== undefined) {
19
+ if (el.getManager && el.getManager()) {
20
+ el.getManager().setSelected(typeof selectedSelected === "string" ? JSON.parse(selectedSelected) : selectedSelected);
21
+ }
22
+ else {
23
+ el.setAttribute("selected", typeof selectedSelected === "string" ? selectedSelected : JSON.stringify(selectedSelected));
24
+ }
25
+ }
26
+ }, [selectedSelected]);
27
+ React.useLayoutEffect(() => {
28
+ const el = internalRef.current;
29
+ if (el && el.getManager && el.getManager()) {
30
+ const mgr = el.getManager();
31
+ const sub = mgr.getSubscriber();
32
+ if (sub) {
33
+ const unbinds = [];
34
+ if (onDelete) {
35
+ unbinds.push(sub.bind("onDelete", (id) => onDelete(id)));
36
+ }
37
+ if (onClear) {
38
+ unbinds.push(sub.bind("onClear", () => onClear()));
39
+ }
40
+ if (onChange) {
41
+ unbinds.push(sub.bind("onChange", (s) => onChange(s)));
42
+ }
43
+ if (onInputChange) {
44
+ unbinds.push(sub.bind("onInputChange", (e, previousValue) => onInputChange({
45
+ originalEvent: e,
46
+ value: e.target.value,
47
+ key: e.key || "",
48
+ previousValue,
49
+ })));
50
+ }
51
+ if (onFocus) {
52
+ unbinds.push(sub.bind("onFocus", (e) => onFocus({ originalEvent: e })));
53
+ }
54
+ return () => {
55
+ unbinds.forEach((u) => u());
56
+ };
57
+ }
58
+ }
59
+ }, [onDelete, onClear, onChange, onInputChange, onFocus]);
60
+ const wcProps = { ...rest, ref: setRef };
61
+ if (label !== undefined)
62
+ wcProps.label = label;
63
+ if (value !== undefined)
64
+ wcProps.value = value;
65
+ if (String(showInput) === "true") {
66
+ wcProps["show-input"] = "true";
67
+ }
68
+ else {
69
+ delete wcProps["show-input"];
70
+ }
71
+ if (String(disabled) === "true") {
72
+ wcProps.disabled = "true";
73
+ }
74
+ else {
75
+ delete wcProps.disabled;
76
+ }
77
+ if (String(error) === "true") {
78
+ wcProps.error = "true";
79
+ }
80
+ else {
81
+ delete wcProps.error;
82
+ }
83
+ if (String(loading) === "true") {
84
+ wcProps.loading = "true";
85
+ }
86
+ else {
87
+ delete wcProps.loading;
88
+ }
89
+ return React.createElement("selected-section", wcProps, children);
90
+ });
91
+ export default SelectedSection;
@@ -0,0 +1,207 @@
1
+ import { SelectedSectionManager } from "./SelectedSectionManager.js";
2
+ /**
3
+ * Injects CSS into the Shadow DOM.
4
+ * Priority:
5
+ * 1. SelectedSection.cssText (Bundler string injection)
6
+ * 2. <meta name="select-component" content="/path1.css, /path2.css"> (Global HTML declaration in main document)
7
+ * 3. SelectedSection.defaultCssUrls (Global JS property)
8
+ *
9
+ * Example of Global HTML Declaration in the main document <head>:
10
+ * <head>
11
+ * <meta name="select-component" content="SelectedSectionManager.css">
12
+ * </head>
13
+ */
14
+ export class SelectedSection extends HTMLElement {
15
+ _manager = null;
16
+ _options = {};
17
+ _mountPoint;
18
+ _stylesInjected = false;
19
+ // 1. For Bundlers: Assign CSS string directly (e.g. import css from './style.css?raw')
20
+ static cssText = "";
21
+ // 2. For Vanilla JS: Default URLs (Vite/Webpack5 will also process import.meta.url)
22
+ static defaultCssUrls = [];
23
+ static get observedAttributes() {
24
+ return ["label", "show-input", "value", "disabled", "error", "loading", "selected"];
25
+ }
26
+ constructor() {
27
+ super();
28
+ this.attachShadow({ mode: "open" });
29
+ this.shadowRoot.innerHTML = `<style></style><div></div>`;
30
+ this._mountPoint = this.shadowRoot.querySelector("div");
31
+ }
32
+ connectedCallback() {
33
+ this._injectStyles();
34
+ if (this._manager)
35
+ return;
36
+ this._options = {
37
+ label: this.getAttribute("label") || "",
38
+ showInput: this.hasAttribute("show-input"),
39
+ value: this.getAttribute("value") || "",
40
+ disabled: this.hasAttribute("disabled"),
41
+ error: this.hasAttribute("error"),
42
+ loading: this.hasAttribute("loading"),
43
+ selected: this._parseJSON(this.getAttribute("selected")) ?? [],
44
+ };
45
+ this._manager = new SelectedSectionManager(this._mountPoint, this._options);
46
+ }
47
+ disconnectedCallback() {
48
+ this._manager?.destroy();
49
+ this._manager = null;
50
+ }
51
+ attributeChangedCallback(name, _oldValue, newValue) {
52
+ if (!this._manager)
53
+ return;
54
+ const isTrue = this.hasAttribute(name);
55
+ switch (name) {
56
+ case "label":
57
+ this._manager.setLabel(newValue);
58
+ break;
59
+ case "show-input":
60
+ this._manager.setShowInput(isTrue);
61
+ break;
62
+ case "value":
63
+ this._manager.setValue(newValue);
64
+ break;
65
+ case "disabled":
66
+ this._manager.setDisabled(isTrue);
67
+ break;
68
+ case "error":
69
+ this._manager.setError(isTrue);
70
+ break;
71
+ case "loading":
72
+ this._manager.setLoading(isTrue);
73
+ break;
74
+ case "selected": {
75
+ const parsed = this._parseJSON(newValue);
76
+ if (parsed !== undefined) {
77
+ this._manager.setSelected(parsed);
78
+ }
79
+ break;
80
+ }
81
+ }
82
+ }
83
+ _injectStyles() {
84
+ if (this._stylesInjected)
85
+ return;
86
+ this._stylesInjected = true;
87
+ const style = document.createElement("style");
88
+ // Scenario A: Bundler injected raw CSS string directly
89
+ if (SelectedSection.cssText) {
90
+ style.textContent = SelectedSection.cssText;
91
+ }
92
+ // Scenario B: Load from URLs (Global Meta Tag > Default Static Property)
93
+ else {
94
+ let urls = [];
95
+ const metaTag = document.querySelector('meta[name="select-component"]');
96
+ if (metaTag && metaTag.getAttribute("content")) {
97
+ urls = metaTag
98
+ .getAttribute("content")
99
+ .split(",")
100
+ .map((s) => s.trim());
101
+ }
102
+ else {
103
+ urls = SelectedSection.defaultCssUrls;
104
+ }
105
+ urls.forEach((url) => {
106
+ if (!url)
107
+ return;
108
+ style.textContent += `@import url("${url}");\n`;
109
+ });
110
+ }
111
+ // Remove existing injected CSS if updating dynamically
112
+ const existingStyle = this.shadowRoot.querySelector("style");
113
+ if (existingStyle) {
114
+ existingStyle.remove();
115
+ }
116
+ this.shadowRoot.appendChild(style);
117
+ }
118
+ // Proxied methods
119
+ setSelected(list) {
120
+ this._manager?.setSelected(list);
121
+ }
122
+ setValue(value) {
123
+ this._manager?.setValue(value);
124
+ }
125
+ clearSearch(triggerOnChange = true) {
126
+ this._manager?.clearSearch(triggerOnChange);
127
+ }
128
+ setError(state) {
129
+ this.toggleAttribute("error", state);
130
+ }
131
+ setDisabled(state) {
132
+ this.toggleAttribute("disabled", state);
133
+ }
134
+ setLoading(state) {
135
+ this.toggleAttribute("loading", state);
136
+ }
137
+ setLabel(text) {
138
+ this.setAttribute("label", text);
139
+ }
140
+ setShowInput(state) {
141
+ this.toggleAttribute("show-input", state);
142
+ }
143
+ setRenderItem(renderer) {
144
+ this._manager?.setRenderItem(renderer);
145
+ }
146
+ setRenderList(renderer) {
147
+ this._manager?.setRenderList(renderer);
148
+ }
149
+ render() {
150
+ this._manager?.render();
151
+ }
152
+ // Getters and setters for properties
153
+ get selected() {
154
+ return this._manager?.getSelected() || [];
155
+ }
156
+ set selected(val) {
157
+ this.setSelected(val);
158
+ }
159
+ get value() {
160
+ return this._manager?.propInputElement?.value || "";
161
+ }
162
+ set value(val) {
163
+ this.setValue(val);
164
+ }
165
+ get label() {
166
+ return this.getAttribute("label") || "";
167
+ }
168
+ set label(val) {
169
+ this.setLabel(val);
170
+ }
171
+ get error() {
172
+ return this.hasAttribute("error");
173
+ }
174
+ set error(val) {
175
+ this.setError(val);
176
+ }
177
+ get disabled() {
178
+ return this.hasAttribute("disabled");
179
+ }
180
+ set disabled(val) {
181
+ this.setDisabled(val);
182
+ }
183
+ get loading() {
184
+ return this.hasAttribute("loading");
185
+ }
186
+ set loading(val) {
187
+ this.setLoading(val);
188
+ }
189
+ setFocus() {
190
+ this._manager?.setFocus();
191
+ }
192
+ getManager() {
193
+ return this._manager;
194
+ }
195
+ _parseJSON(val) {
196
+ if (!val)
197
+ return undefined;
198
+ try {
199
+ return JSON.parse(val);
200
+ }
201
+ catch (e) {
202
+ console.error(`SelectedSection: failed to parse JSON:`, val, e);
203
+ return undefined;
204
+ }
205
+ }
206
+ }
207
+ customElements.define("selected-section", SelectedSection);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ export function clickOutside(targets, callback) {
2
+ const targetList = Array.isArray(targets) ? targets : [targets];
3
+ const handler = (e) => {
4
+ const node = e.target;
5
+ const isInside = targetList.some((target) => target && target.contains(node));
6
+ if (!isInside) {
7
+ callback(e);
8
+ }
9
+ };
10
+ // Use capturing phase so we don't miss events if some child stops propagation
11
+ document.addEventListener("click", handler, true);
12
+ document.addEventListener("touchstart", handler, { capture: true, passive: true });
13
+ return function unbind() {
14
+ document.removeEventListener("click", handler, true);
15
+ document.removeEventListener("touchstart", handler, true);
16
+ };
17
+ }
@@ -0,0 +1,15 @@
1
+ /** @es.ts
2
+ {
3
+ mode: "bundle",
4
+ extension: ".js",
5
+ options: {
6
+ }
7
+ }
8
+ @es.ts */
9
+ export * from "./container/ContainerManager.js";
10
+ export * from "./options-section/OptionsSectionManager.js";
11
+ export * from "./selected-section/SelectedSectionManager.js";
12
+ export * from "./unbind/clickOutside.js";
13
+ export * from "./composite-select/helpers.js";
14
+ export * from "./composite-select/CompositeManager.js";
15
+ export * from "./types.js";
@@ -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
+ }