composite-select 1.0.2 → 1.0.4

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 (37) hide show
  1. package/README.md +4 -0
  2. package/composition/list-up-down-navi/ListManager.css +203 -0
  3. package/package.json +1 -1
  4. package/commitlint.config.js +0 -3
  5. package/composition/composite-select/CompositeManager.js +0 -43
  6. package/composition/composite-select/composite-select.js +0 -199
  7. package/composition/composite-select/debounce.js +0 -10
  8. package/composition/composite-select/helpers.js +0 -96
  9. package/composition/composite-select/react.js +0 -189
  10. package/composition/container/ContainerManager.js +0 -76
  11. package/composition/img/ai.png +0 -0
  12. package/composition/img/chatgpt.png +0 -0
  13. package/composition/img/claude.png +0 -0
  14. package/composition/img/gemini.png +0 -0
  15. package/composition/img/gmail.png +0 -0
  16. package/composition/img/google_calendar.png +0 -0
  17. package/composition/img/google_drive.png +0 -0
  18. package/composition/img/google_keep.png +0 -0
  19. package/composition/img/img.json +0 -5
  20. package/composition/img/perplexity.png +0 -0
  21. package/composition/img/t3chat.png +0 -0
  22. package/composition/img/timeanddate.png +0 -0
  23. package/composition/img/tools.png +0 -0
  24. package/composition/img/youtube.png +0 -0
  25. package/composition/options-section/OptionsSectionManager.js +0 -486
  26. package/composition/options-section/options-section.js +0 -245
  27. package/composition/options-section/react.js +0 -90
  28. package/composition/selected-section/SelectedSectionManager.js +0 -336
  29. package/composition/selected-section/react.js +0 -91
  30. package/composition/selected-section/selected-section.js +0 -207
  31. package/composition/unbind/clickOutside.js +0 -17
  32. package/diff/coreBundle.patch +0 -13
  33. package/diff/recorderApp.patch +0 -13
  34. package/madooei.tar.gz +0 -0
  35. package/release.config.js +0 -3
  36. package/test/lib.d.ts +0 -6
  37. package/test/lib.js +0 -30
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ # npm
2
+
3
+ https://www.npmjs.com/package/composite-select
4
+
1
5
  # DEV
2
6
 
3
7
  - [DEV.md](./DEV.md)
@@ -0,0 +1,203 @@
1
+ @keyframes spin {
2
+ from {
3
+ transform: rotate(0deg);
4
+ }
5
+ to {
6
+ transform: rotate(360deg);
7
+ }
8
+ }
9
+
10
+ .options {
11
+ flex: 1;
12
+ overflow-y: auto;
13
+ min-height: 0;
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: 2px;
17
+ padding: 8px;
18
+ background: #fff;
19
+ border: 1px solid #ccc;
20
+ border-radius: 4px;
21
+
22
+ .element {
23
+ cursor: pointer;
24
+ padding: 8px 12px;
25
+ border-left: 3px solid transparent;
26
+ transition: background-color 150ms;
27
+ display: flex;
28
+ align-items: center;
29
+ font-size: 14px;
30
+ color: #3c4043;
31
+ user-select: none;
32
+
33
+ & > * {
34
+ pointer-events: none;
35
+ }
36
+
37
+ &:hover {
38
+ background: rgba(0, 0, 0, 0.04);
39
+ }
40
+
41
+ &.selected {
42
+ background: rgba(26, 115, 232, 0.08);
43
+ border-left-color: #1a73e8;
44
+ color: #1a73e8;
45
+ }
46
+
47
+ &.highlighted {
48
+ outline: 2px solid #1a73e8;
49
+ outline-offset: -2px;
50
+ }
51
+ }
52
+
53
+ .empty-msg {
54
+ padding: 24px;
55
+ text-align: center;
56
+ color: #5f6368;
57
+ font-style: italic;
58
+ font-size: 14px;
59
+ }
60
+ }
61
+
62
+ .filter {
63
+ flex-shrink: 0;
64
+ padding: 12px 12px 8px;
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 8px;
68
+ border: 1px solid #ccc;
69
+ border-bottom: none;
70
+ border-radius: 4px 4px 0 0;
71
+ background: #fff;
72
+ }
73
+
74
+ .input-wrapper {
75
+ position: relative;
76
+ display: grid;
77
+ flex: 1;
78
+
79
+ &::after {
80
+ content: "";
81
+ grid-area: 1 / 1;
82
+ border-radius: 4px;
83
+ box-shadow: 0 0 0 1px #99999b;
84
+ transition: box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1);
85
+ pointer-events: none;
86
+ z-index: 0;
87
+ }
88
+
89
+ &:focus-within::after {
90
+ box-shadow: 0 0 0 2px #1a73e8;
91
+ }
92
+
93
+ label {
94
+ grid-area: 1 / 1;
95
+ align-self: start;
96
+ justify-self: start;
97
+ margin-left: 7px;
98
+ margin-top: 9px;
99
+ font-size: 16px;
100
+ line-height: 1;
101
+ color: #5f6368;
102
+ pointer-events: none;
103
+ transition:
104
+ transform 150ms,
105
+ font-size 150ms,
106
+ color 150ms,
107
+ margin-top 150ms;
108
+ background: #fff;
109
+ padding: 0 4px;
110
+ z-index: 3;
111
+ transform: translateY(0);
112
+ }
113
+
114
+ input {
115
+ grid-area: 1 / 1;
116
+ width: 100%;
117
+ border: none;
118
+ background: transparent;
119
+ padding: 8px 12px;
120
+ padding-right: 36px;
121
+ font-size: 16px;
122
+ font-family: inherit;
123
+ outline: none;
124
+ box-sizing: border-box;
125
+ z-index: 2;
126
+
127
+ &::placeholder {
128
+ color: transparent;
129
+ }
130
+
131
+ &:not(:placeholder-shown) + label,
132
+ &:focus + label {
133
+ margin-top: 0;
134
+ transform: translateY(-50%);
135
+ font-size: 12px;
136
+ }
137
+
138
+ &:focus + label {
139
+ color: #1a73e8;
140
+ }
141
+ }
142
+ }
143
+
144
+ .spinner {
145
+ position: absolute;
146
+ right: 10px;
147
+ top: 50%;
148
+ margin-top: -11px;
149
+ width: 22px;
150
+ height: 22px;
151
+ background-image: url("data:image/svg+xml,%3Csvg fill='%231a73e8' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z'/%3E%3C/svg%3E");
152
+ background-repeat: no-repeat;
153
+ background-size: contain;
154
+ animation: spin 0.75s linear infinite;
155
+ display: none;
156
+ flex-shrink: 0;
157
+ z-index: 4;
158
+
159
+ &.loading {
160
+ display: block;
161
+ }
162
+ }
163
+
164
+ .gcp-css {
165
+ font-family: inherit;
166
+ font-size: 14px;
167
+ font-weight: 500;
168
+ height: 36px;
169
+ padding: 0 16px;
170
+ border-radius: 4px;
171
+ cursor: pointer;
172
+ transition:
173
+ background-color 150ms,
174
+ box-shadow 150ms;
175
+ display: inline-flex;
176
+ align-items: center;
177
+ justify-content: center;
178
+ border: 1px solid transparent;
179
+ outline: none;
180
+ box-sizing: border-box;
181
+ background-color: #1a73e8;
182
+ color: #fff;
183
+ margin-right: 8px;
184
+ margin-bottom: 8px;
185
+
186
+ &:hover {
187
+ background-color: #1765cc;
188
+ box-shadow:
189
+ 0 1px 2px 0 rgba(66, 133, 244, 0.3),
190
+ 0 1px 3px 1px rgba(66, 133, 244, 0.15);
191
+ }
192
+
193
+ &.white {
194
+ background-color: transparent;
195
+ color: #1a73e8;
196
+ border-color: #ccc;
197
+
198
+ &:hover {
199
+ background-color: rgba(26, 115, 232, 0.04);
200
+ box-shadow: none;
201
+ }
202
+ }
203
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "composite-select",
4
- "version": "1.0.2",
4
+ "version": "1.0.4",
5
5
  "author": "Szymon Działowski",
6
6
  "description": "Select dropdown component, vanilla, web components and react",
7
7
  "license": "MIT",
@@ -1,3 +0,0 @@
1
- export default {
2
- extends: ["@commitlint/config-conventional"],
3
- };
@@ -1,43 +0,0 @@
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
- }
@@ -1,199 +0,0 @@
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);
@@ -1,10 +0,0 @@
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
- }
@@ -1,96 +0,0 @@
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
- }