kr-elements 0.0.1-alpha.1

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.
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HTMLComboboxOptionElement = void 0;
4
+ const Boolean_attribute_value_normalizer_js_1 = require("./Boolean.attribute.value.normalizer.js");
5
+ class HTMLComboboxOptionElement extends HTMLElement {
6
+ static booleanAttributes = new Set(['selected']);
7
+ static stringAttributes = new Set(['value', 'label']);
8
+ connectedCallback() {
9
+ this.#initialAttributesSynchronization();
10
+ super.setAttribute('part', 'box-option');
11
+ super.setAttribute('tabindex', "0");
12
+ super.setAttribute('role', "option");
13
+ if (this.children.length === 0) {
14
+ this.textContent = this.label;
15
+ }
16
+ }
17
+ get value() {
18
+ return this.getAttribute('value');
19
+ }
20
+ set value(value) {
21
+ value = this.#getOrExtractValue(value, 'value');
22
+ super.setAttribute('value', value);
23
+ }
24
+ get label() {
25
+ return this.getAttribute('value');
26
+ }
27
+ set label(value) {
28
+ value = this.#getOrExtractValue(value, 'label');
29
+ super.setAttribute('label', value);
30
+ }
31
+ get selected() {
32
+ return this.hasAttribute('selected');
33
+ }
34
+ set selected(value) {
35
+ super.toggleAttribute('selected', (0, Boolean_attribute_value_normalizer_js_1.toBoolean)(value));
36
+ }
37
+ #initialAttributesSynchronization() {
38
+ for (const key of HTMLComboboxOptionElement.booleanAttributes) {
39
+ const value = (0, Boolean_attribute_value_normalizer_js_1.toBoolean)(this.getAttribute(key));
40
+ Reflect.set(this, key, value);
41
+ }
42
+ for (const key of HTMLComboboxOptionElement.stringAttributes) {
43
+ Reflect.set(this, key, this.getAttribute(key));
44
+ }
45
+ }
46
+ get #optionHasOnlyTextNode() {
47
+ return this.children.length === 0 && this.textContent.length > 0;
48
+ }
49
+ #getOrExtractValue(value, name) {
50
+ if (typeof value === 'string')
51
+ return value;
52
+ if (value == null) {
53
+ const opposite = name === 'label' ? 'value' : 'label';
54
+ const oppositeValue = this.getAttribute(opposite);
55
+ if (oppositeValue) {
56
+ value = oppositeValue;
57
+ }
58
+ else {
59
+ if (this.#optionHasOnlyTextNode) {
60
+ value = this.textContent;
61
+ }
62
+ else {
63
+ throw new TypeError('No value, or label or textContent found');
64
+ }
65
+ }
66
+ return value;
67
+ }
68
+ throw new TypeError('Invalid value');
69
+ }
70
+ setAttribute(name, value) {
71
+ if (HTMLComboboxOptionElement.booleanAttributes.has(name)) {
72
+ Reflect.set(this, name, (0, Boolean_attribute_value_normalizer_js_1.toBoolean)(value));
73
+ return;
74
+ }
75
+ if (HTMLComboboxOptionElement.stringAttributes.has(name)) {
76
+ Reflect.set(this, name, value);
77
+ return;
78
+ }
79
+ super.setAttribute(name, value);
80
+ }
81
+ removeAttribute(name) {
82
+ if (HTMLComboboxOptionElement.stringAttributes.has(name)) {
83
+ Reflect.set(this, name, false);
84
+ return;
85
+ }
86
+ if (HTMLComboboxOptionElement.stringAttributes.has(name)) {
87
+ return;
88
+ }
89
+ super.removeAttribute(name);
90
+ }
91
+ }
92
+ exports.HTMLComboboxOptionElement = HTMLComboboxOptionElement;
93
+ if (!window.customElements.get('box-option')) {
94
+ window.customElements.define('box-option', HTMLComboboxOptionElement);
95
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HTMLComboboxTagElement = void 0;
4
+ class HTMLComboboxTagElement extends HTMLElement {
5
+ connectedCallback() {
6
+ this.part.add('box-tag');
7
+ if (this.parentElement) {
8
+ if (this.parentElement.hasAttribute('multiple')) {
9
+ if (!this.querySelector('[part="tag-clear-button"]')) {
10
+ throw new Error(`A <button> with part="tag-clear-button" is required for <combo-box> with multiple attribute`);
11
+ }
12
+ }
13
+ if (!this.querySelector('[part="tag-label"]')) {
14
+ throw new Error(`Invalid <box-tag-template>, an element with part="tag-label" is required as a descendant`);
15
+ }
16
+ }
17
+ }
18
+ }
19
+ exports.HTMLComboboxTagElement = HTMLComboboxTagElement;
20
+ if (!window.customElements.get('box-tag')) {
21
+ window.customElements.define('box-tag', HTMLComboboxTagElement);
22
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./HTML.combobox.element.js"), exports);
@@ -0,0 +1,7 @@
1
+ export function toBoolean(value) {
2
+ if (value == null || value === 'false' || value === false)
3
+ value = false;
4
+ if (value === true || value === 'true' || value === '')
5
+ value = true;
6
+ return Boolean(value);
7
+ }
@@ -0,0 +1,311 @@
1
+ import { HTMLComboboxTagElement } from './HTML.combobox.tag.element.js';
2
+ export class ComboboxMarkup {
3
+ #shadowRoot;
4
+ #internals;
5
+ tagsContainer = null;
6
+ optionsContainer = null;
7
+ clearAllButton = null;
8
+ dropdown = null;
9
+ placeholder = null;
10
+ searchInput = null;
11
+ constructor(shadowRoot, internals) {
12
+ this.#shadowRoot = shadowRoot;
13
+ this.#internals = internals;
14
+ this.#shadowRoot.host.addEventListener('focus', this.showDropdown);
15
+ this.#shadowRoot.host.addEventListener('blur', this.hideDropdown);
16
+ this.#shadowRoot.host.addEventListener('click', this.showDropdown);
17
+ document.addEventListener('click', this.hideDropdown);
18
+ document.addEventListener('scroll', this.onPageScroll);
19
+ }
20
+ connect() {
21
+ this.tagsContainer = this.#shadowRoot.querySelector('#tags');
22
+ this.optionsContainer = this.#shadowRoot.querySelector('[part="options"]');
23
+ this.clearAllButton = this.#shadowRoot.querySelector('[part="clear-all-button"]');
24
+ this.dropdown = this.#shadowRoot.querySelector('#dropdown');
25
+ this.placeholder = this.#shadowRoot.querySelector('#placeholder');
26
+ this.searchInput = this.#shadowRoot.querySelector('[part="search-input"]');
27
+ }
28
+ sort(query) {
29
+ const regex = new RegExp(query, 'i');
30
+ this.optionsContainer.querySelectorAll('box-option')
31
+ .forEach(option => {
32
+ if (query === '') {
33
+ option.style.display = "initial";
34
+ }
35
+ else if (!regex.test(option.label)) {
36
+ option.style.display = "none";
37
+ }
38
+ else {
39
+ option.style.order = "initial";
40
+ }
41
+ });
42
+ }
43
+ disconnect() {
44
+ this.#shadowRoot.host.removeEventListener('focus', this.showDropdown);
45
+ this.#shadowRoot.host.removeEventListener('blur', this.hideDropdown);
46
+ this.#shadowRoot.host.removeEventListener('click', this.showDropdown);
47
+ document.removeEventListener('click', this.hideDropdown);
48
+ document.removeEventListener('scroll', this.onPageScroll);
49
+ }
50
+ onPageScroll = () => {
51
+ if (this.dropdown.matches(':popover-open')) {
52
+ this.setDropdownPosition(this.#shadowRoot.host.getBoundingClientRect());
53
+ }
54
+ };
55
+ setDropdownPosition(rect) {
56
+ const dropdown = this.dropdown;
57
+ const vh = window.innerHeight;
58
+ const sh = rect.height;
59
+ const sy = rect.top;
60
+ if (sy < (vh - vh / 3)) {
61
+ dropdown.style.top = sh + sy + 'px';
62
+ dropdown.style.bottom = 'unset';
63
+ dropdown.style.transform = `unset`;
64
+ }
65
+ else {
66
+ dropdown.style.top = sh + sy + 'px';
67
+ dropdown.style.bottom = 'unset';
68
+ dropdown.style.transform = `translateY(calc(-1 * calc(100% + ${sh}px)))`;
69
+ }
70
+ dropdown.style.left = rect.left + 'px';
71
+ dropdown.style.width = rect.width + 'px';
72
+ dropdown.style.maxHeight = '50vh';
73
+ }
74
+ showDropdown = () => {
75
+ try {
76
+ this.setDropdownPosition(this.#shadowRoot.host.getBoundingClientRect());
77
+ this.dropdown.style.display = 'flex';
78
+ this.dropdown.showPopover();
79
+ this.#internals.ariaExpanded = "true";
80
+ }
81
+ catch {
82
+ this.#internals.ariaExpanded = "false";
83
+ }
84
+ };
85
+ hideDropdown = (event) => {
86
+ if (event.composedPath().includes(this.#shadowRoot.host))
87
+ return;
88
+ try {
89
+ this.dropdown.hidePopover();
90
+ this.dropdown.style.display = 'none';
91
+ this.#internals.ariaExpanded = "false";
92
+ }
93
+ catch {
94
+ this.#internals.ariaExpanded = "true";
95
+ }
96
+ };
97
+ createAndAppendTag(option) {
98
+ const template = this.tagTemplate;
99
+ const tag = template.cloneNode(true);
100
+ const label = tag.querySelector('[part="tag-label"]');
101
+ label.textContent = option.label;
102
+ const clearButton = tag.querySelector('[part="tag-clear-button"]');
103
+ clearButton.setAttribute('value', option.value);
104
+ tag.setAttribute('value', option.value);
105
+ option.querySelectorAll('[part]')
106
+ .forEach(node => {
107
+ const relatedPart = tag.querySelector(`[part=${node.getAttribute('part')}]`);
108
+ if (relatedPart) {
109
+ tag.replaceChild(node.cloneNode(true), relatedPart);
110
+ }
111
+ });
112
+ this.tagsContainer.appendChild(tag);
113
+ return clearButton;
114
+ }
115
+ getTagByValue(value) {
116
+ return this.tagsContainer.querySelector(`box-tag[value="${value}"]`);
117
+ }
118
+ getOptionByValue(value) {
119
+ return this.optionsContainer.querySelector(`box-option[value="${value}"]`);
120
+ }
121
+ get tagTemplate() {
122
+ let template = this.#shadowRoot.host.firstElementChild;
123
+ if (!template || !(template instanceof HTMLComboboxTagElement)) {
124
+ const innerTemplate = this.#shadowRoot.querySelector('#tag-template');
125
+ const doc = document.importNode(innerTemplate.content, true);
126
+ template = doc.querySelector('box-tag');
127
+ }
128
+ return template;
129
+ }
130
+ get selectedOptions() {
131
+ return this.optionsContainer
132
+ .querySelectorAll('box-option[selected]');
133
+ }
134
+ static get template() {
135
+ return `
136
+ <style>
137
+ :host {
138
+ font-size: inherit;
139
+ font-family: inherit;
140
+ display: grid;
141
+ grid-template-columns: minmax(0, max-content) 1fr;
142
+ align-items: center;
143
+ gap: 1px;
144
+ position: relative;
145
+ }
146
+
147
+ :host([multiple]) {
148
+ grid-template-columns: minmax(0, max-content) 1fr auto;
149
+ }
150
+
151
+ #dropdown {
152
+ inset: unset;
153
+ margin: 0;
154
+ box-sizing: border-box;
155
+ overflow-y: scroll;
156
+ flex-direction: column;
157
+ border-radius: inherit;
158
+ }
159
+
160
+ [part="options"] {
161
+ display: flex;
162
+ flex-direction: column;
163
+ gap: 2px;
164
+ padding-block: .5rem;
165
+ border-radius: inherit;
166
+ }
167
+
168
+ [part="options"] box-option {
169
+ border-radius: inherit;
170
+ content-visibility: auto;
171
+ border: 1px solid cornflowerblue;
172
+ }
173
+
174
+ [part="options"] box-option[selected] {
175
+ background-color: Highlight;
176
+ }
177
+
178
+ [part="search-input"] {
179
+ display: none;
180
+ position: sticky;
181
+ top: 0;
182
+ z-index: 2;
183
+ border-radius: inherit;
184
+ border-style: inherit;
185
+ border-width: inherit;
186
+ padding: inherit;
187
+ }
188
+
189
+ :host([searchable]) [part="search-input"],
190
+ :host([filterable]) [part="search-input"] {
191
+ display: flex;
192
+ }
193
+
194
+ #placeholder {
195
+ overflow: hidden;
196
+ }
197
+
198
+ #tags:not(:empty) + #placeholder {
199
+ display: none;
200
+ }
201
+
202
+ #tags:not(:empty) {
203
+ grid-column: 1 / span 2;
204
+ width: 100%;
205
+ }
206
+
207
+ #tags {
208
+ display: flex;
209
+ flex-wrap: wrap;
210
+ overflow: hidden;
211
+ gap: 2px;
212
+ border-radius: inherit;
213
+ }
214
+
215
+ [part="tag"] {
216
+ width: 100%;
217
+ justify-self: start;
218
+ font-size: inherit;
219
+ box-sizing: border-box;
220
+ display: flex;
221
+ align-items: center;
222
+ border-radius: inherit;
223
+ padding-inline-start: 0.2lh;
224
+ padding-inline-end: .2rem;
225
+ background-color: transparent;
226
+ gap: 5px;
227
+ }
228
+
229
+ :host([multiple]) [part="tag"] {
230
+ background-color: Highlight;
231
+ width: fit-content;
232
+ max-width: 100%;
233
+ }
234
+
235
+ [part="tag-label"] {
236
+ white-space: nowrap;
237
+ text-overflow: ellipsis;
238
+ overflow: hidden;
239
+ user-select: none;
240
+ font-size: 95%;
241
+ flex-grow: 1;
242
+ }
243
+
244
+ :host([multiple]) [part="tag-label"] {
245
+ flex-grow: unset;
246
+ }
247
+
248
+ [part="tag-clear-button"], [part="clear-all-button"] {
249
+ border-radius: 100%;
250
+ border: none;
251
+ aspect-ratio: 1;
252
+ line-height: 0;
253
+ padding: 0!important;
254
+ user-select: none;
255
+ background-color: transparent;
256
+ }
257
+
258
+ [part="tag-clear-button"] {
259
+ inline-size: 1em;
260
+ block-size: 1em;
261
+ font-size: 80%;
262
+ display: none;
263
+ }
264
+
265
+ :host([multiple]) [part="tag-clear-button"],
266
+ :host([clearable]) [part="tag-clear-button"] {
267
+ display: block;
268
+ }
269
+
270
+ [part="clear-all-button"] {
271
+ font-size: inherit;
272
+ inline-size: 1.2em;
273
+ block-size: 1.2em;
274
+ display: none;
275
+ }
276
+
277
+ :host([multiple]) [part="clear-all-button"] {
278
+ display: block;
279
+ }
280
+
281
+ [part="clear-all-button"]:hover,
282
+ [part="tag-clear-button"]:hover {
283
+ color: ActiveText;
284
+ }
285
+
286
+ [part="clear-all-button"]:hover {
287
+ background-color: ButtonFace;
288
+ }
289
+
290
+ :host:has(#tags:empty) [part="clear-all-button"] {
291
+ /*display: none;*/
292
+ }
293
+
294
+ </style>
295
+
296
+ <div id="tags"></div>
297
+ <div id="placeholder" ></div>
298
+ <button part="clear-all-button">✕</button>
299
+ <div id="dropdown" popover="manual">
300
+ <input name="search-input" part="search-input" />
301
+ <div part="options"></div>
302
+ </div>
303
+ <template id='tag-template'>
304
+ <box-tag>
305
+ <span part="tag-label"></span>
306
+ <button part="tag-clear-button">✕</button>
307
+ </box-tag>
308
+ </template>
309
+ `;
310
+ }
311
+ }