kr-elements 0.0.1-alpha.1 → 0.0.1-alpha.2

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