@kubex/zinc 1.1.39 → 1.1.40

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.
@@ -153,9 +153,39 @@ export default class ZnDatepicker extends ZincElement implements ZincFormControl
153
153
  */
154
154
  @property() format: string = 'MM/dd/yyyy';
155
155
 
156
+ /** Display time selector. **/
157
+ @property({attribute: 'time-picker',type: Boolean, reflect: true}) timePicker?: boolean = false;
158
+
159
+ /** Display only time selector, without date. **/
160
+ @property({attribute: 'only-time'}) onlyTimepicker?: boolean = false;
161
+
162
+ /**
163
+ * Time format for display and input selector. Uses AirDatepicker format tokens.
164
+ * Default : hh:mm AA
165
+ *
166
+ * Possible symbols:
167
+ * h — hours in 12-hour mode
168
+ * hh — hours in 12-hour mode with leading zero
169
+ * H — hours in 24-hour mode
170
+ * HH — hours in 24-hour mode with leading zero
171
+ * m — minutes
172
+ * mm — minutes with leading zero
173
+ * aa — day period lower case
174
+ * AA — day period upper case
175
+ */
176
+ @property({attribute: 'format-time'}) timeFormat?: string = "hh:mm AA";
177
+ @property({attribute: 'container'}) container?: string | HTMLElement;
178
+
179
+
180
+
156
181
  private _instance: AirDatepicker<HTMLInputElement>;
157
182
 
158
183
 
184
+ get timestamp(): number {
185
+ const timestamp = (this.input.parentElement!.querySelector("#date-picker-timestamp")! as HTMLInputElement).value ?? '';
186
+ return Number(timestamp);
187
+ }
188
+
159
189
  /** Gets the validity state object */
160
190
  get validity() {
161
191
  return this.input?.validity;
@@ -194,6 +224,8 @@ export default class ZnDatepicker extends ZincElement implements ZincFormControl
194
224
  @watch('minDate', {waitUntilFirstUpdate: true})
195
225
  @watch('maxDate', {waitUntilFirstUpdate: true})
196
226
  @watch('disablePastDates', {waitUntilFirstUpdate: true})
227
+ @watch('timePicker', {waitUntilFirstUpdate: true})
228
+ @watch('timeFormat', {waitUntilFirstUpdate: true})
197
229
  handleDatepickerOptionsChange() {
198
230
  this.init();
199
231
  }
@@ -241,24 +273,44 @@ export default class ZnDatepicker extends ZincElement implements ZincFormControl
241
273
  }
242
274
  }
243
275
 
244
- const inputElement = this.shadowRoot?.querySelector('input') as HTMLInputElement;
276
+ const inputElement = this.shadowRoot!.querySelector('input')! as HTMLInputElement;
245
277
  if (inputElement) {
278
+ const timestampField = this.shadowRoot!.querySelector("#date-picker-timestamp")! as HTMLElement;
279
+
246
280
  const options: AirDatepickerOptions = {
247
281
  locale: enLocale,
248
282
  dateFormat: this.format,
249
283
  range: this.range,
250
284
  toggleSelected: false,
285
+ timepicker: this.timePicker,
286
+ onlyTimepicker: this.onlyTimepicker,
287
+ timeFormat: this.timeFormat,
288
+ container: this.container,
289
+ altField: timestampField,
251
290
  onSelect: ({date}) => {
291
+ // Blur the input after selection to prevent invisible keyboard navigation
292
+ if (this.timePicker || this.onlyTimepicker){
293
+ return
294
+ }
295
+ if (date && !this.range) {
296
+ // For single date selection, blur immediately
297
+ this.input.blur();
252
298
  this.handleChange();
253
- // Blur the input after selection to prevent invisible keyboard navigation
254
- if (date && !this.range) {
255
- // For single date selection, blur immediately
256
- this.input.blur();
257
- } else if (date && this.range && Array.isArray(date) && date.length === 2) {
258
- // For range selection, blur only after both dates are selected
259
- this.input.blur();
260
- }
299
+ } else if (date && this.range && Array.isArray(date) && date.length === 2) {
300
+ // For range selection, blur only after both dates are selected
301
+ this.input.blur();
302
+ this.handleChange();
261
303
  }
304
+ },
305
+ onHide:(isAnimationComplete) => {
306
+ if (!isAnimationComplete) {
307
+ return
308
+ }
309
+
310
+ if (this.timePicker || this.onlyTimepicker){
311
+ this.handleChange();
312
+ }
313
+ },
262
314
  };
263
315
 
264
316
  if (this.minDate) {
@@ -532,8 +584,11 @@ export default class ZnDatepicker extends ZincElement implements ZincFormControl
532
584
  return formatted;
533
585
  }
534
586
 
535
- protected updated(_changedProperties: PropertyValues) {
587
+ updated(_changedProperties: PropertyValues) {
536
588
  super.updated(_changedProperties);
589
+ }
590
+
591
+ firstUpdated(){
537
592
  this.init();
538
593
  }
539
594
 
@@ -617,13 +672,12 @@ export default class ZnDatepicker extends ZincElement implements ZincFormControl
617
672
  ?autofocus=${this.autofocus}
618
673
  spellcheck=${this.spellcheck}
619
674
  aria-describedby="help-text"
620
- @change=${this.handleChange}
621
675
  @input=${this.handleInput}
622
676
  @invalid=${this.handleInvalid}
623
677
  @keydown=${this.handleKeyDown}
624
678
  @paste=${this.handlePaste}
625
679
  @blur=${this.handleBlur}>
626
-
680
+ <input id="date-picker-timestamp" type="hidden">
627
681
  <span part="suffix" class="input__suffix">
628
682
  <slot name="suffix"></slot>
629
683
  </span>
@@ -1,6 +1,7 @@
1
1
  @use "../../wc";
2
2
  @use "../../form-control";
3
3
 
4
+
4
5
  :host {
5
6
  display: block;
6
7
  }
@@ -13,6 +13,7 @@ import type {ZnChangeEvent} from "../../events/zn-change";
13
13
  import type {ZnInputEvent} from "../../events/zn-input";
14
14
 
15
15
  import styles from './query-builder.scss';
16
+ import type ZnDatepicker from "../datepicker";
16
17
 
17
18
  export type QueryBuilderData = QueryBuilderItem[];
18
19
 
@@ -22,10 +23,24 @@ export interface QueryBuilderItem {
22
23
  type?: QueryBuilderType;
23
24
  options?: QueryBuilderOptions;
24
25
  operators: QueryBuilderOperators[];
26
+ dateSubmitFormat?: QueryBuilderDateSubmitFormat;
25
27
  maxOptionsVisible?: string;
26
28
  }
27
29
 
28
- export type QueryBuilderType = 'bool' | 'boolean' | 'date' | 'number';
30
+ /**
31
+ * Controls how `date` and `dateTime` filter values are serialized when the
32
+ * query is submitted.
33
+ *
34
+ * - `'iso'` — RFC 3339 / ISO 8601 (e.g. `2026-06-09T16:05:00Z`).
35
+ * - `'timestamp'` — Unix timestamp in seconds since epoch.
36
+ * - `'legacy'` — whatever format the current system emits. Kept so existing
37
+ * backends keep working while consumers migrate to one of the
38
+ * formats above. - DEFAULT
39
+ */
40
+ export type QueryBuilderDateSubmitFormat = 'iso' | 'timestamp' | 'legacy';
41
+
42
+ export type QueryBuilderType = 'bool' | 'boolean' | 'date' | 'dateTime' | 'number';
43
+
29
44
 
30
45
  export interface QueryBuilderOptions {
31
46
  [key: string | number]: string | number;
@@ -187,6 +202,7 @@ export default class ZnQueryBuilder extends ZincElement implements ZincFormContr
187
202
  </zn-option>`)}
188
203
  </zn-select>
189
204
  <input id="main-input" name="${this.name}" value="${this.value}" hidden>
205
+ <div id="air-datepicker-query-builder-container" class="air-datepicker-global-container"></div>
190
206
  </div>
191
207
  `;
192
208
  }
@@ -214,6 +230,7 @@ export default class ZnQueryBuilder extends ZincElement implements ZincFormContr
214
230
  if (filter === undefined) return;
215
231
 
216
232
  const uniqueId = Math.random().toString(36).substring(7);
233
+
217
234
  this._selectedRules.set(uniqueId, {
218
235
  id: filter.id,
219
236
  name: filter.name,
@@ -306,7 +323,7 @@ export default class ZnQueryBuilder extends ZincElement implements ZincFormContr
306
323
  }
307
324
 
308
325
  private _createInput(filter: QueryBuilderItem, uniqueId: string, selectedComparator: QueryBuilderOperators) {
309
- let input: ZnSelect | ZnInput | null;
326
+ let input: ZnSelect | ZnInput | ZnDatepicker | null;
310
327
  switch (filter.type) {
311
328
  case 'bool':
312
329
  case 'boolean': {
@@ -318,7 +335,11 @@ export default class ZnQueryBuilder extends ZincElement implements ZincFormContr
318
335
  break;
319
336
  }
320
337
  case 'date': {
321
- input = this._createDateInput(uniqueId);
338
+ input = this._createDateInput(uniqueId, false, filter.dateSubmitFormat);
339
+ break;
340
+ }
341
+ case 'dateTime': {
342
+ input = this._createDateInput(uniqueId, true , filter.dateSubmitFormat);
322
343
  break;
323
344
  }
324
345
  default: {
@@ -373,13 +394,19 @@ export default class ZnQueryBuilder extends ZincElement implements ZincFormContr
373
394
  return litToHTML<ZnInput>(input);
374
395
  }
375
396
 
376
- private _createDateInput(uniqueId: string): ZnInput | null {
397
+ private _createDateInput(uniqueId: string, hasTime: boolean, submitFormat: QueryBuilderDateSubmitFormat = 'legacy'): ZnDatepicker | null {
377
398
  const input = html`
378
- <zn-input type="date"
379
- class="query-builder__value"
380
- @zn-input="${(e: ZnInputEvent) => this._updateDateValue(uniqueId, e)}">
381
- </zn-input>`;
382
- return litToHTML<ZnInput>(input);
399
+ <zn-datepicker
400
+ name="${uniqueId}"
401
+ format-time="HH:mm"
402
+ .container=${this.container.querySelector('#air-datepicker-query-builder-container')}
403
+ time-picker="${hasTime}"
404
+ class="query-builder__value"
405
+ @zn-change="${(e: ZnChangeEvent) => this._updateDateValue(uniqueId, e, submitFormat)}"
406
+ >
407
+ </zn-datepicker>
408
+ `;
409
+ return litToHTML<ZnDatepicker>(input);
383
410
  }
384
411
 
385
412
  private _createSelectInput(uniqueId: string, filter: QueryBuilderItem, selectedComparator: QueryBuilderOperators): ZnSelect | null {
@@ -426,23 +453,36 @@ export default class ZnQueryBuilder extends ZincElement implements ZincFormContr
426
453
  this._handleChange();
427
454
  }
428
455
 
429
- private _updateDateValue(id: string, event: Event | { target: ZnSelect | ZnInput | HTMLDivElement }) {
456
+ private _updateDateValue(id: string, event: Event | { target: ZnDatepicker | HTMLDivElement }, submitFormat: QueryBuilderDateSubmitFormat = 'legacy') {
430
457
  const filter = this._selectedRules.get(id);
431
458
  if (!filter) return;
432
- const input = event.target as ZnSelect | ZnInput;
433
- const operator = filter.operator as QueryBuilderOperators;
434
- let timestamp: string;
435
459
 
436
- if (operator === QueryBuilderOperators.Eq || operator === QueryBuilderOperators.Neq) {
437
- timestamp = (Date.parse(input.value as string) / 1000).toString();
438
- } else {
439
- // Dodgy logic to offset backend filter comparator values
440
- // Ref: backend/src/Infrastructure/Helpers/AdvancedFilterHelper.php:106
441
- const multiplier = operator === QueryBuilderOperators.Before ? -1 : 1;
442
- timestamp = (Math.floor((Date.now() - Date.parse(input.value as string)) / 1000 / 60) * multiplier).toString();
460
+ const input = event.target as ZnDatepicker;
461
+ const operator = filter.operator as QueryBuilderOperators;
462
+ let d: Date;
463
+ let value: string;
464
+
465
+ switch (submitFormat) {
466
+ case "legacy":
467
+ if (operator === QueryBuilderOperators.Eq || operator === QueryBuilderOperators.Neq) {
468
+ value = (input.timestamp / 1000).toString();
469
+ } else {
470
+ // Dodgy logic to offset backend filter comparator values
471
+ // Ref: backend/src/Infrastructure/Helpers/AdvancedFilterHelper.php:106
472
+ const multiplier = operator === QueryBuilderOperators.Before ? -1 : 1;
473
+ value = (Math.floor((Date.now() - input.timestamp) / 1000 / 60) * multiplier).toString();
474
+ }
475
+ break;
476
+ case "timestamp":
477
+ value = input.timestamp.toString();
478
+ break;
479
+ case "iso":
480
+ d = new Date(input.timestamp);
481
+ value = new Date(d.getTime() - d.getTimezoneOffset() * 60_000).toISOString();
482
+ break;
443
483
  }
444
484
 
445
- filter.value = timestamp as string;
485
+ filter.value = value;
446
486
 
447
487
  this._selectedRules.set(id, filter);
448
488
  this._handleChange();
@@ -459,6 +499,7 @@ export default class ZnQueryBuilder extends ZincElement implements ZincFormContr
459
499
  this._handleChange();
460
500
  }
461
501
 
502
+
462
503
  private updateInValue(id: string, event: Event) {
463
504
  const filter = this._selectedRules.get(id);
464
505
  if (!filter) return;
@@ -1,6 +1,10 @@
1
1
  @use "../../wc";
2
2
  @use "../../../scss/shared/form-elements";
3
3
 
4
+
5
+ @use "../../../scss/air-datapicker";
6
+
7
+
4
8
  :host {
5
9
  display: contents;
6
10
 
@@ -1,10 +1,11 @@
1
1
  import {type CSSResultGroup, unsafeCSS} from 'lit';
2
+ import {customElement, property} from 'lit/decorators.js';
2
3
  import {LocalizeController} from '../../utilities/localize';
3
- import {property} from 'lit/decorators.js';
4
4
  import ZincElement from '../../internal/zinc-element';
5
5
 
6
6
  import styles from './style.scss';
7
7
 
8
+ @customElement('zn-style')
8
9
  export default class ZnStyle extends ZincElement {
9
10
  static styles: CSSResultGroup = unsafeCSS(styles);
10
11
 
@@ -12,7 +13,8 @@ export default class ZnStyle extends ZincElement {
12
13
  private readonly localize = new LocalizeController(this);
13
14
 
14
15
  @property() color = '';
15
- @property({type: Boolean}) border = false;
16
+ @property() border = '';
17
+ @property() size = '';
16
18
  @property({type: Boolean}) error = false;
17
19
  @property({type: Boolean}) success = false;
18
20
  @property({type: Boolean}) info = false;
@@ -26,6 +28,7 @@ export default class ZnStyle extends ZincElement {
26
28
  @property() height = '';
27
29
  @property() pad = '';
28
30
  @property() margin = '';
31
+ @property({type: Boolean}) muted = false;
29
32
  @property({type: Boolean}) gutter = false;
30
33
  @property({attribute: 'a-margin'}) autoMargin = '';
31
34
 
@@ -81,9 +84,34 @@ export default class ZnStyle extends ZincElement {
81
84
  this.classList.toggle('h-full', true);
82
85
  }
83
86
 
84
- if (this.border) {
87
+ // border accepts any combination of t/b/l/r, plus 'a' (or 'tblr') as a
88
+ // shortcut for all four sides. Existing `<zn-style border>` (boolean
89
+ // attribute → empty string value with attribute present) keeps rendering
90
+ // all four sides; pure absence renders no border.
91
+ const borderSides = this.hasAttribute('border')
92
+ ? (this.border || 'a')
93
+ : '';
94
+ if (borderSides) {
85
95
  display = 'inline-block'
86
- this.classList.toggle('zn-border', true);
96
+ for (const c of borderSides) {
97
+ switch (c) {
98
+ case 'a':
99
+ this.classList.toggle('zn-border', true);
100
+ break;
101
+ case 't':
102
+ this.classList.toggle('zn-bt', true);
103
+ break;
104
+ case 'b':
105
+ this.classList.toggle('zn-bb', true);
106
+ break;
107
+ case 'l':
108
+ this.classList.toggle('zn-bl', true);
109
+ break;
110
+ case 'r':
111
+ this.classList.toggle('zn-br', true);
112
+ break;
113
+ }
114
+ }
87
115
  }
88
116
 
89
117
  if (this.pad) {
@@ -174,6 +202,17 @@ export default class ZnStyle extends ZincElement {
174
202
  }
175
203
  }
176
204
 
205
+ if (this.muted) {
206
+ display = 'inline-block';
207
+ this.classList.toggle('zn-muted', true);
208
+ }
209
+
210
+ // size: xs/s/l/xl each toggle a sizing class. 'm' (and empty) are the
211
+ // default and apply no class.
212
+ if (this.size && this.size !== 'm') {
213
+ this.classList.toggle('zn-size-' + this.size, true);
214
+ }
215
+
177
216
  if (this.display) {
178
217
  // Force attribute display
179
218
  display = this.display
@@ -7,4 +7,75 @@ describe('<zn-style>', () => {
7
7
 
8
8
  expect(el).to.exist;
9
9
  });
10
+
11
+ describe('border', () => {
12
+ it('border="t" applies only zn-bt', async () => {
13
+ const el = await fixture<HTMLElement>(html`<zn-style border="t"></zn-style>`);
14
+ expect(el.classList.contains('zn-bt')).to.be.true;
15
+ expect(el.classList.contains('zn-bb')).to.be.false;
16
+ expect(el.classList.contains('zn-bl')).to.be.false;
17
+ expect(el.classList.contains('zn-br')).to.be.false;
18
+ expect(el.classList.contains('zn-border')).to.be.false;
19
+ });
20
+
21
+ it('border="tb" applies zn-bt and zn-bb', async () => {
22
+ const el = await fixture<HTMLElement>(html`<zn-style border="tb"></zn-style>`);
23
+ expect(el.classList.contains('zn-bt')).to.be.true;
24
+ expect(el.classList.contains('zn-bb')).to.be.true;
25
+ expect(el.classList.contains('zn-bl')).to.be.false;
26
+ expect(el.classList.contains('zn-br')).to.be.false;
27
+ });
28
+
29
+ it('border (attribute present but empty) applies zn-border (all four sides)', async () => {
30
+ const el = await fixture<HTMLElement>(html`<zn-style border></zn-style>`);
31
+ expect(el.classList.contains('zn-border')).to.be.true;
32
+ });
33
+
34
+ it('no border attribute applies no border classes', async () => {
35
+ const el = await fixture<HTMLElement>(html`<zn-style></zn-style>`);
36
+ expect(el.classList.contains('zn-border')).to.be.false;
37
+ expect(el.classList.contains('zn-bt')).to.be.false;
38
+ expect(el.classList.contains('zn-bb')).to.be.false;
39
+ expect(el.classList.contains('zn-bl')).to.be.false;
40
+ expect(el.classList.contains('zn-br')).to.be.false;
41
+ });
42
+ });
43
+
44
+ describe('size', () => {
45
+ it('size="s" applies zn-size-s', async () => {
46
+ const el = await fixture<HTMLElement>(html`<zn-style size="s"></zn-style>`);
47
+ expect(el.classList.contains('zn-size-s')).to.be.true;
48
+ });
49
+
50
+ it('size="xs" applies zn-size-xs', async () => {
51
+ const el = await fixture<HTMLElement>(html`<zn-style size="xs"></zn-style>`);
52
+ expect(el.classList.contains('zn-size-xs')).to.be.true;
53
+ });
54
+
55
+ it('size="xl" applies zn-size-xl', async () => {
56
+ const el = await fixture<HTMLElement>(html`<zn-style size="xl"></zn-style>`);
57
+ expect(el.classList.contains('zn-size-xl')).to.be.true;
58
+ });
59
+
60
+ it('size="m" applies no size class', async () => {
61
+ const el = await fixture<HTMLElement>(html`<zn-style size="m"></zn-style>`);
62
+ expect(el.classList.contains('zn-size-m')).to.be.false;
63
+ expect(el.classList.contains('zn-size-xs')).to.be.false;
64
+ expect(el.classList.contains('zn-size-s')).to.be.false;
65
+ expect(el.classList.contains('zn-size-l')).to.be.false;
66
+ expect(el.classList.contains('zn-size-xl')).to.be.false;
67
+ });
68
+ });
69
+
70
+ describe('muted', () => {
71
+ it('applies zn-muted class when present', async () => {
72
+ const el = await fixture<HTMLElement>(html`<zn-style muted></zn-style>`);
73
+ expect(el.classList.contains('zn-muted')).to.be.true;
74
+ });
75
+
76
+ it('does not apply zn-muted when absent', async () => {
77
+ const el = await fixture<HTMLElement>(html`<zn-style></zn-style>`);
78
+ expect(el.classList.contains('zn-muted')).to.be.false;
79
+ });
80
+ });
10
81
  });
@@ -0,0 +1,63 @@
1
+ import {expect, fixture, html} from '@open-wc/testing';
2
+ import {deepQuery} from './query';
3
+
4
+ describe('deepQuery', () => {
5
+ it('returns null when no element matches', async () => {
6
+ await fixture(html`<div></div>`);
7
+ expect(deepQuery('.does-not-exist')).to.be.null;
8
+ });
9
+
10
+ it('finds an element in the light DOM of document', async () => {
11
+ const el = await fixture<HTMLElement>(html`<div class="needle"></div>`);
12
+ expect(deepQuery('.needle')).to.equal(el);
13
+ });
14
+
15
+ it('finds an element nested inside a shadow root', async () => {
16
+ const host = await fixture<HTMLElement>(html`<div></div>`);
17
+ const shadow = host.attachShadow({mode: 'open'});
18
+ const needle = document.createElement('span');
19
+ needle.className = 'needle';
20
+ shadow.appendChild(needle);
21
+
22
+ expect(deepQuery('.needle')).to.equal(needle);
23
+ });
24
+
25
+ it('finds an element across two nested shadow roots', async () => {
26
+ const outer = await fixture<HTMLElement>(html`<div></div>`);
27
+ const outerShadow = outer.attachShadow({mode: 'open'});
28
+
29
+ const inner = document.createElement('div');
30
+ outerShadow.appendChild(inner);
31
+ const innerShadow = inner.attachShadow({mode: 'open'});
32
+
33
+ const needle = document.createElement('span');
34
+ needle.className = 'needle';
35
+ innerShadow.appendChild(needle);
36
+
37
+ expect(deepQuery('.needle')).to.equal(needle);
38
+ });
39
+
40
+ it('returns the first match in document order', async () => {
41
+ const root = await fixture<HTMLElement>(html`
42
+ <div>
43
+ <span class="needle" data-which="light"></span>
44
+ </div>
45
+ `);
46
+ const shadow = root.attachShadow({mode: 'open'});
47
+ const inShadow = document.createElement('span');
48
+ inShadow.className = 'needle';
49
+ inShadow.setAttribute('data-which', 'shadow');
50
+ shadow.appendChild(inShadow);
51
+
52
+ const hit = deepQuery<HTMLElement>('.needle');
53
+ expect(hit?.getAttribute('data-which')).to.equal('light');
54
+ });
55
+
56
+ it('accepts a custom root', async () => {
57
+ const a = await fixture<HTMLElement>(html`<div><span class="needle"></span></div>`);
58
+ const b = await fixture<HTMLElement>(html`<div></div>`);
59
+
60
+ expect(deepQuery('.needle', a)).to.exist;
61
+ expect(deepQuery('.needle', b)).to.be.null;
62
+ });
63
+ });
@@ -1,3 +1,30 @@
1
+ export function deepQuery<T extends Element = Element>(
2
+ selector: string,
3
+ root: Document | ShadowRoot | Element = document,
4
+ ): T | null {
5
+ const direct = root.querySelector<T>(selector);
6
+ if (direct) return direct;
7
+ for (const el of root.querySelectorAll<Element>('*')) {
8
+ if (el.shadowRoot) {
9
+ const hit = deepQuery<T>(selector, el.shadowRoot);
10
+ if (hit) return hit;
11
+ }
12
+ }
13
+ return null;
14
+ }
15
+
16
+ export function deepQueryAll<T extends Element = Element>(
17
+ selector: string,
18
+ root: Document | ShadowRoot | Element = document,
19
+ out: T[] = [],
20
+ ): T[] {
21
+ out.push(...root.querySelectorAll<T>(selector));
22
+ for (const el of root.querySelectorAll<Element>('*')) {
23
+ if (el.shadowRoot) deepQueryAll<T>(selector, el.shadowRoot, out);
24
+ }
25
+ return out;
26
+ }
27
+
1
28
  export function deepQuerySelectorAll(selector: string, element: Element, stopSelector: string): Element[] {
2
29
  const arr: Element[] = [];
3
30