@kubex/zinc 1.1.39 → 1.1.41
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.
- package/dist/custom-elements.json +321 -20
- package/dist/vscode.html-custom-data.json +38 -4
- package/dist/web-types.json +100 -6
- package/dist/zn.d.ts +236 -173
- package/dist/zn.min.css +1 -1
- package/dist/zn.min.js +541 -521
- package/docs/_utilities/code-previews.cjs +6 -2
- package/docs/pages/components/datepicker.md +10 -1
- package/docs/pages/components/editor.md +17 -0
- package/docs/pages/components/style.md +46 -0
- package/package.json +1 -1
- package/scss/_root.scss +21 -1
- package/scss/air-datapicker.scss +50 -0
- package/scss/boot.scss +1 -48
- package/src/components/data-table/data-table.component.ts +152 -73
- package/src/components/data-table/data-table.scss +6 -0
- package/src/components/data-table/data-table.test.ts +15 -0
- package/src/components/data-table-search/data-table-search.component.ts +13 -1
- package/src/components/datepicker/datepicker.component.ts +66 -12
- package/src/components/datepicker/datepicker.scss +1 -0
- package/src/components/editor/editor.component.ts +51 -6
- package/src/components/editor/editor.test.ts +62 -1
- package/src/components/query-builder/query-builder.component.ts +62 -21
- package/src/components/query-builder/query-builder.scss +4 -0
- package/src/components/style/style.component.ts +43 -4
- package/src/components/style/style.test.ts +71 -0
- package/src/utilities/query.test.ts +63 -0
- package/src/utilities/query.ts +27 -0
|
@@ -14,4 +14,19 @@ describe('<zn-data-table>', () => {
|
|
|
14
14
|
|
|
15
15
|
expect(() => (el as unknown as { updateKeys: () => void }).updateKeys()).not.to.throw();
|
|
16
16
|
});
|
|
17
|
+
|
|
18
|
+
it('accepts a single Row object on the data property', async () => {
|
|
19
|
+
const el = await fixture<ZnDataTable>(html` <zn-data-table></zn-data-table> `);
|
|
20
|
+
(el as unknown as {data: unknown}).data = {
|
|
21
|
+
id: '1',
|
|
22
|
+
cells: [{text: 'Solo', column: 'name'}],
|
|
23
|
+
};
|
|
24
|
+
expect(() => (el as unknown as {requestUpdate: () => void}).requestUpdate()).not.to.throw();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('exposes displayTemplates as an object property', async () => {
|
|
28
|
+
const el = await fixture<ZnDataTable>(html` <zn-data-table></zn-data-table> `);
|
|
29
|
+
const templates = (el as unknown as {displayTemplates: Record<string, unknown>}).displayTemplates;
|
|
30
|
+
expect(templates).to.be.an('object');
|
|
31
|
+
});
|
|
17
32
|
});
|
|
@@ -98,7 +98,19 @@ export default class ZnDataTableSearch extends ZincElement implements ZincFormCo
|
|
|
98
98
|
if (!slot) return params;
|
|
99
99
|
|
|
100
100
|
const elements = slot.assignedElements({flatten: true});
|
|
101
|
-
const allowedInputs = [
|
|
101
|
+
const allowedInputs = [
|
|
102
|
+
'zn-input',
|
|
103
|
+
'zn-select',
|
|
104
|
+
'zn-query-builder',
|
|
105
|
+
'zn-multiselect',
|
|
106
|
+
'zn-params-select',
|
|
107
|
+
'zn-datepicker',
|
|
108
|
+
'input',
|
|
109
|
+
'select',
|
|
110
|
+
'textarea',
|
|
111
|
+
'zn-cols',
|
|
112
|
+
'zn-input-group',
|
|
113
|
+
];
|
|
102
114
|
|
|
103
115
|
elements.forEach((element) => {
|
|
104
116
|
if (allowedInputs.includes(element.tagName.toLowerCase())) {
|
|
@@ -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
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
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>
|
|
@@ -3,6 +3,7 @@ import {deepQuerySelectorAll} from "../../utilities/query";
|
|
|
3
3
|
import {FormControlController} from '../../internal/form';
|
|
4
4
|
import {on} from "../../utilities/on";
|
|
5
5
|
import {property, query} from 'lit/decorators.js';
|
|
6
|
+
import {Store} from "../../internal/storage";
|
|
6
7
|
import Attachment from "./modules/attachment/attachment";
|
|
7
8
|
import ContextMenu from "./modules/context-menu/context-menu";
|
|
8
9
|
import DatePicker from "./modules/date-picker/date-picker";
|
|
@@ -90,6 +91,21 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
|
|
|
90
91
|
@property({attribute: 'ai', type: Boolean}) aiEnabled: boolean = false;
|
|
91
92
|
@property({attribute: 'ai-path'}) aiPath: string = '';
|
|
92
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Caches unsent content while typing and restores it when the editor next loads,
|
|
96
|
+
* so agent replies in tickets/chats survive reloads and navigation. Use a key
|
|
97
|
+
* unique to the conversation (e.g. the ticket ID). The cache is cleared on submit.
|
|
98
|
+
*/
|
|
99
|
+
@property({attribute: 'store-key', reflect: true}) storeKey: string = "";
|
|
100
|
+
|
|
101
|
+
/** Cached-content expiry in seconds. Defaults to 1 day. */
|
|
102
|
+
@property({attribute: 'store-ttl', type: Number, reflect: true}) storeTtl = 86400;
|
|
103
|
+
|
|
104
|
+
/** Cache to localStorage instead of sessionStorage, persisting across tabs and browser restarts. */
|
|
105
|
+
@property({attribute: 'local-storage', type: Boolean, reflect: true}) localStorage: boolean;
|
|
106
|
+
|
|
107
|
+
protected _store: Store;
|
|
108
|
+
|
|
93
109
|
private quillElement: Quill;
|
|
94
110
|
private _content: string = '';
|
|
95
111
|
private _selectionRange: Range = {index: 0, length: 0};
|
|
@@ -119,6 +135,11 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
|
|
|
119
135
|
this.formControlController.updateValidity();
|
|
120
136
|
}
|
|
121
137
|
|
|
138
|
+
connectedCallback() {
|
|
139
|
+
super.connectedCallback();
|
|
140
|
+
this._store = new Store(this.localStorage ? window.localStorage : window.sessionStorage, "zned:", this.storeTtl);
|
|
141
|
+
}
|
|
142
|
+
|
|
122
143
|
protected firstUpdated(_changedProperties: PropertyValues) {
|
|
123
144
|
this.formControlController.updateValidity();
|
|
124
145
|
|
|
@@ -261,6 +282,7 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
|
|
|
261
282
|
this.quillElement = quill;
|
|
262
283
|
|
|
263
284
|
this.getForm()?.addEventListener('submit', () => {
|
|
285
|
+
this._clearCachedContent();
|
|
264
286
|
setTimeout(() => {
|
|
265
287
|
const attachmentModule = this.quillElement.getModule('attachment') as Attachment;
|
|
266
288
|
attachmentModule?.reset();
|
|
@@ -372,6 +394,14 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
|
|
|
372
394
|
this._selectionRange = range ?? oldRange;
|
|
373
395
|
});
|
|
374
396
|
|
|
397
|
+
if (this.storeKey && this._isEmpty(this.value ?? '')) {
|
|
398
|
+
const cached = this._store.get(this.storeKey);
|
|
399
|
+
if (cached) {
|
|
400
|
+
this.value = cached;
|
|
401
|
+
this.editorHtml.value = cached;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
375
405
|
const delta = quill.clipboard.convert({html: this.value});
|
|
376
406
|
quill.setContents(delta, Quill.sources.SILENT);
|
|
377
407
|
|
|
@@ -382,15 +412,30 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
|
|
|
382
412
|
private _handleTextChange() {
|
|
383
413
|
this.value = this.quillElement.getSemanticHTML();
|
|
384
414
|
this.editorHtml.value = this.value;
|
|
415
|
+
this._cacheContent();
|
|
385
416
|
this.emit('zn-change');
|
|
386
417
|
}
|
|
387
418
|
|
|
388
|
-
private
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
419
|
+
private _isEmpty(value: string) {
|
|
420
|
+
return value.match(/[^<pbr\s>/]/) === null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private _cacheContent() {
|
|
424
|
+
if (!this.storeKey || !this._store) return;
|
|
425
|
+
if (this._isEmpty(this.value)) {
|
|
426
|
+
this._store.remove(this.storeKey);
|
|
427
|
+
} else {
|
|
428
|
+
this._store.set(this.storeKey, this.value);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
393
431
|
|
|
432
|
+
private _clearCachedContent() {
|
|
433
|
+
if (this.storeKey && this._store) {
|
|
434
|
+
this._store.remove(this.storeKey);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
private _getQuillKeyboardBindings() {
|
|
394
439
|
// Always add an Enter binding to support emoji selection in all interaction types
|
|
395
440
|
return {
|
|
396
441
|
'enter': {
|
|
@@ -406,7 +451,7 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
|
|
|
406
451
|
|
|
407
452
|
if (this.interactionType === 'chat') {
|
|
408
453
|
const form = this.closest('form');
|
|
409
|
-
const hasText = !!this.value && this.value.trim().length > 0 && !
|
|
454
|
+
const hasText = !!this.value && this.value.trim().length > 0 && !this._isEmpty(this.value);
|
|
410
455
|
const attachmentInput = form?.querySelector('input[name="attachments"]') as HTMLInputElement | null;
|
|
411
456
|
const hasAttachments = !!attachmentInput?.value && attachmentInput.value !== '[]';
|
|
412
457
|
if (form && (hasText || hasAttachments)) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import '../../../dist/zn.min.js';
|
|
2
|
-
import {
|
|
2
|
+
import {expect, fixture, html} from '@open-wc/testing';
|
|
3
|
+
import type ZnEditor from './editor.component';
|
|
3
4
|
|
|
4
5
|
describe('<zn-editor>', () => {
|
|
5
6
|
it('should render a component', async () => {
|
|
@@ -7,4 +8,64 @@ describe('<zn-editor>', () => {
|
|
|
7
8
|
|
|
8
9
|
expect(el).to.exist;
|
|
9
10
|
});
|
|
11
|
+
|
|
12
|
+
describe('content caching', () => {
|
|
13
|
+
const KEY = 'zned:test-draft';
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
sessionStorage.removeItem(KEY);
|
|
17
|
+
localStorage.removeItem(KEY);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should restore cached content into an empty editor', async () => {
|
|
21
|
+
sessionStorage.setItem(KEY, '0,<p>Hello draft</p>');
|
|
22
|
+
const el = await fixture<ZnEditor>(html`
|
|
23
|
+
<zn-editor store-key="test-draft"></zn-editor>`);
|
|
24
|
+
|
|
25
|
+
expect(el.value).to.contain('Hello draft');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should not overwrite an initial value with cached content', async () => {
|
|
29
|
+
sessionStorage.setItem(KEY, '0,<p>Hello draft</p>');
|
|
30
|
+
const el = await fixture<ZnEditor>(html`
|
|
31
|
+
<zn-editor store-key="test-draft" .value=${'<p>Existing</p>'}></zn-editor>`);
|
|
32
|
+
|
|
33
|
+
expect(el.value).to.contain('Existing');
|
|
34
|
+
expect(el.value).to.not.contain('Hello draft');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should read from localStorage when local-storage is set', async () => {
|
|
38
|
+
localStorage.setItem(KEY, '0,<p>Local draft</p>');
|
|
39
|
+
const el = await fixture<ZnEditor>(html`
|
|
40
|
+
<zn-editor store-key="test-draft" local-storage></zn-editor>`);
|
|
41
|
+
|
|
42
|
+
expect(el.value).to.contain('Local draft');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should cache content when the editor changes', async () => {
|
|
46
|
+
const el = await fixture<ZnEditor>(html`
|
|
47
|
+
<zn-editor store-key="test-draft" .value=${'<p>Typed reply</p>'}></zn-editor>`);
|
|
48
|
+
|
|
49
|
+
document.dispatchEvent(new Event('zn-editor-update'));
|
|
50
|
+
await el.updateComplete;
|
|
51
|
+
|
|
52
|
+
// Quill's getSemanticHTML encodes spaces as
|
|
53
|
+
expect(sessionStorage.getItem(KEY)).to.contain('Typed reply');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should clear cached content on form submit', async () => {
|
|
57
|
+
const form = await fixture<HTMLFormElement>(html`
|
|
58
|
+
<form @submit=${(e: Event) => e.preventDefault()}>
|
|
59
|
+
<zn-editor store-key="test-draft" .value=${'<p>Typed reply</p>'}></zn-editor>
|
|
60
|
+
</form>`);
|
|
61
|
+
const el = form.querySelector<ZnEditor>('zn-editor')!;
|
|
62
|
+
|
|
63
|
+
document.dispatchEvent(new Event('zn-editor-update'));
|
|
64
|
+
await el.updateComplete;
|
|
65
|
+
expect(sessionStorage.getItem(KEY)).to.contain('Typed reply');
|
|
66
|
+
|
|
67
|
+
form.requestSubmit();
|
|
68
|
+
expect(sessionStorage.getItem(KEY)).to.equal(null);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
10
71
|
});
|
|
@@ -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
|
-
|
|
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):
|
|
397
|
+
private _createDateInput(uniqueId: string, hasTime: boolean, submitFormat: QueryBuilderDateSubmitFormat = 'legacy'): ZnDatepicker | null {
|
|
377
398
|
const input = html`
|
|
378
|
-
<zn-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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:
|
|
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
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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 =
|
|
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,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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|