@kubex/zinc 1.0.113 → 1.0.114
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/.github/workflows/eleventy_build.yml +5 -5
- package/.github/workflows/js_build_and_deploy.yaml +1 -1
- package/dist/custom-elements.json +2636 -421
- package/dist/vscode.html-custom-data.json +96 -16
- package/dist/web-types.json +268 -31
- package/dist/zn.d.ts +517 -340
- package/dist/zn.min.css +1 -1
- package/dist/zn.min.js +942 -863
- package/docs/eleventy.config.cjs +4 -2
- package/docs/pages/components/header.md +13 -0
- package/docs/pages/components/inline-edit.md +20 -0
- package/docs/pages/components/item.md +12 -1
- package/docs/pages/components/page.md +149 -0
- package/docs/pages/components/progress-bar.md +16 -0
- package/docs/pages/components/select.md +25 -0
- package/docs/pages/components/split-pane.md +6 -7
- package/docs/pages/components/tabs.md +59 -9
- package/package.json +8 -5
- package/scripts/build.js +40 -19
- package/scss/_global-spacing.scss +12 -1
- package/scss/_root.scss +34 -2
- package/scss/shared/layout.scss +15 -2
- package/scss/themes/_light.scss +1 -1
- package/src/components/absolute-container/absolute-container.component.ts +9 -25
- package/src/components/animated-button/animated-button.test.ts +8 -8
- package/src/components/button/button.component.ts +3 -1
- package/src/components/button/button.scss +5 -0
- package/src/components/button-menu/button-menu.component.ts +36 -21
- package/src/components/button-menu/button-menu.test.ts +55 -1
- package/src/components/chart/chart.component.ts +7 -7
- package/src/components/collapsible/collapsible.component.ts +7 -14
- package/src/components/cols/cols.scss +1 -1
- package/src/components/content-block/content-block.component.ts +11 -33
- package/src/components/data-select/data-select.component.ts +7 -11
- package/src/components/data-table/data-table.component.ts +10 -14
- package/src/components/editor/modules/toolbar/toolbar.component.ts +5 -9
- package/src/components/expanding-action/expanding-action.component.ts +37 -43
- package/src/components/form-group/form-group.component.ts +8 -3
- package/src/components/header/header.component.ts +6 -5
- package/src/components/header/header.scss +31 -25
- package/src/components/inline-edit/inline-edit.component.ts +13 -1
- package/src/components/inline-edit/inline-edit.test.ts +50 -0
- package/src/components/input/input.test.ts +77 -0
- package/src/components/input-group/input-group.test.ts +2 -10
- package/src/components/item/item.component.ts +12 -6
- package/src/components/item/item.scss +1 -1
- package/src/components/item/item.test.ts +8 -0
- package/src/components/navbar/navbar.component.ts +222 -48
- package/src/components/navbar/navbar.scss +134 -21
- package/src/components/navbar/navbar.test.ts +114 -9
- package/src/components/option/option.scss +5 -7
- package/src/components/option/option.test.ts +30 -0
- package/src/components/page/index.ts +13 -0
- package/src/components/page/page.component.ts +406 -0
- package/src/components/page/page.scss +325 -0
- package/src/components/page/page.test.ts +397 -0
- package/src/components/page-nav/page-nav.scss +7 -5
- package/src/components/pane/pane.component.ts +2 -2
- package/src/components/pane/pane.scss +0 -1
- package/src/components/pane/pane.test.ts +31 -0
- package/src/components/panel/panel.scss +1 -0
- package/src/components/progress-bar/progress-bar.component.ts +5 -2
- package/src/components/progress-bar/progress-bar.scss +17 -1
- package/src/components/progress-bar/progress-bar.test.ts +12 -0
- package/src/components/scroll-container/scroll-container.component.ts +22 -21
- package/src/components/select/select.component.ts +240 -38
- package/src/components/select/select.scss +15 -0
- package/src/components/select/select.test.ts +477 -0
- package/src/components/settings-container/settings-container.component.ts +15 -19
- package/src/components/settings-container/settings-container.scss +8 -8
- package/src/components/sidebar/sidebar.component.ts +11 -11
- package/src/components/simple-chart/simple-chart.component.ts +6 -7
- package/src/components/sp/sp.scss +46 -15
- package/src/components/sp/sp.test.ts +15 -0
- package/src/components/split-pane/split-pane.component.ts +153 -26
- package/src/components/split-pane/split-pane.scss +89 -13
- package/src/components/split-pane/split-pane.test.ts +187 -0
- package/src/components/style/style.component.ts +8 -0
- package/src/components/tab/index.ts +13 -0
- package/src/components/tab/tab.component.ts +25 -0
- package/src/components/tab/tab.scss +7 -0
- package/src/components/table/table.component.ts +7 -2
- package/src/components/tabs/tabs.component.ts +75 -70
- package/src/components/tabs/tabs.scss +1 -1
- package/src/components/textarea/textarea.component.ts +5 -8
- package/src/components/translation-group/translation-group.component.ts +15 -14
- package/src/components/translations/translations.component.ts +18 -17
- package/src/components/translations/translations.scss +1 -0
- package/src/components/translations/translations.test.ts +3 -2
- package/src/internal/form.ts +234 -0
- package/src/internal/theme.ts +26 -46
- package/src/internal/zinc-element.ts +12 -7
- package/src/utilities/form.ts +1 -0
- package/src/wc.scss +1 -1
- package/src/zinc.ts +3 -0
|
@@ -6,6 +6,7 @@ import {ifDefined} from 'lit/directives/if-defined.js';
|
|
|
6
6
|
import {keyed} from 'lit/directives/keyed.js';
|
|
7
7
|
import {live} from 'lit/directives/live.js';
|
|
8
8
|
import {property, state} from 'lit/decorators.js';
|
|
9
|
+
import {ResizeController} from '@lit-labs/observers/resize-controller.js';
|
|
9
10
|
import ZincElement from '../../internal/zinc-element';
|
|
10
11
|
import ZnButton from '../button';
|
|
11
12
|
import ZnButtonGroup from '../button-group';
|
|
@@ -53,10 +54,26 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
53
54
|
@state() private _activeLanguage = 'en';
|
|
54
55
|
@state() private _overflowIndex = -1;
|
|
55
56
|
|
|
56
|
-
private _resizeObserver?: ResizeObserver;
|
|
57
57
|
private _lastObservedWidth = 0;
|
|
58
58
|
private _measureRafId = 0;
|
|
59
59
|
|
|
60
|
+
constructor() {
|
|
61
|
+
super();
|
|
62
|
+
// eslint-disable-next-line no-new
|
|
63
|
+
new ResizeController(this, {
|
|
64
|
+
callback: entries => {
|
|
65
|
+
const width = entries[0]?.contentRect.width ?? 0;
|
|
66
|
+
if (Math.abs(width - this._lastObservedWidth) < 1) return;
|
|
67
|
+
this._lastObservedWidth = width;
|
|
68
|
+
if (this._overflowIndex !== -1) {
|
|
69
|
+
this._overflowIndex = -1;
|
|
70
|
+
} else {
|
|
71
|
+
this._scheduleLangOverflow();
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
60
77
|
get validity(): ValidityState {
|
|
61
78
|
return validValidityState;
|
|
62
79
|
}
|
|
@@ -106,23 +123,8 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
106
123
|
return Object.keys(this.values);
|
|
107
124
|
}
|
|
108
125
|
|
|
109
|
-
connectedCallback() {
|
|
110
|
-
super.connectedCallback();
|
|
111
|
-
this._resizeObserver = new ResizeObserver(entries => {
|
|
112
|
-
const width = entries[0]?.contentRect.width ?? 0;
|
|
113
|
-
if (Math.abs(width - this._lastObservedWidth) < 1) return;
|
|
114
|
-
this._lastObservedWidth = width;
|
|
115
|
-
if (this._overflowIndex !== -1) {
|
|
116
|
-
this._overflowIndex = -1;
|
|
117
|
-
} else {
|
|
118
|
-
this._scheduleLangOverflow();
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
126
|
disconnectedCallback() {
|
|
124
127
|
super.disconnectedCallback();
|
|
125
|
-
this._resizeObserver?.disconnect();
|
|
126
128
|
if (this._measureRafId) {
|
|
127
129
|
cancelAnimationFrame(this._measureRafId);
|
|
128
130
|
this._measureRafId = 0;
|
|
@@ -131,7 +133,6 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
131
133
|
|
|
132
134
|
protected firstUpdated() {
|
|
133
135
|
this.formControlController.updateValidity();
|
|
134
|
-
this._resizeObserver?.observe(this);
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
protected updated(changedProperties: PropertyValues) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import '../../../dist/zn.min.js';
|
|
2
2
|
import { expect, fixture, html } from '@open-wc/testing';
|
|
3
|
+
import type ZnInlineEdit from '../inline-edit/inline-edit.component';
|
|
3
4
|
import type ZnTranslations from './translations.component';
|
|
4
5
|
|
|
5
6
|
describe('<zn-translations>', () => {
|
|
@@ -24,9 +25,9 @@ describe('<zn-translations>', () => {
|
|
|
24
25
|
el.values = { 'en': 'Hello' };
|
|
25
26
|
await el.updateComplete;
|
|
26
27
|
|
|
27
|
-
const input = el.shadowRoot!.querySelector('zn-inline-edit');
|
|
28
|
+
const input = el.shadowRoot!.querySelector('zn-inline-edit') as ZnInlineEdit | null;
|
|
28
29
|
expect(input).to.exist;
|
|
29
|
-
|
|
30
|
+
expect(input!.value).to.equal('Hello');
|
|
30
31
|
|
|
31
32
|
// Simulate change
|
|
32
33
|
input!.value = 'Hello World';
|
package/src/internal/form.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {getFormNavigationController} from "./form-navigation";
|
|
2
|
+
import {Store} from "./storage";
|
|
2
3
|
import type {ReactiveController, ReactiveControllerHost} from "lit";
|
|
3
4
|
import type {ZincFormControl} from "./zinc-element";
|
|
4
5
|
import type Button from "../components/button";
|
|
@@ -16,6 +17,10 @@ export const formCollections: WeakMap<HTMLFormElement, Set<ZincFormControl>> = n
|
|
|
16
17
|
//
|
|
17
18
|
const reportValidityOverloads: WeakMap<HTMLFormElement, () => boolean> = new WeakMap();
|
|
18
19
|
const checkValidityOverloads: WeakMap<HTMLFormElement, () => boolean> = new WeakMap();
|
|
20
|
+
const formPersistenceListeners: WeakMap<HTMLFormElement, {
|
|
21
|
+
handleInput: (event: Event) => void;
|
|
22
|
+
handleChange: (event: Event) => void;
|
|
23
|
+
}> = new WeakMap();
|
|
19
24
|
|
|
20
25
|
//
|
|
21
26
|
// We store a Set of controls that users have interacted with. This allows us to determine the interaction state
|
|
@@ -28,6 +33,184 @@ const userInteractedControls: WeakSet<ZincFormControl> = new WeakSet();
|
|
|
28
33
|
//
|
|
29
34
|
const interactions = new WeakMap<ZincFormControl, string[]>();
|
|
30
35
|
|
|
36
|
+
const formStorePrefix = 'znform:';
|
|
37
|
+
|
|
38
|
+
type StoredFormControlValue = {
|
|
39
|
+
checked?: boolean;
|
|
40
|
+
indeterminate?: boolean;
|
|
41
|
+
value?: unknown;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type PersistableNativeControl = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
|
|
45
|
+
|
|
46
|
+
function getStorage(localStorage = false): Storage | null {
|
|
47
|
+
try {
|
|
48
|
+
return localStorage ? window.localStorage : window.sessionStorage;
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getStore(localStorage = false, ttl = 0): Store | null {
|
|
55
|
+
const storage = getStorage(localStorage);
|
|
56
|
+
return storage ? new Store(storage, formStorePrefix, ttl) : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getStoreTtl(el?: Element | null) {
|
|
60
|
+
const ttl = el?.getAttribute('store-ttl');
|
|
61
|
+
return ttl ? Number(ttl) || 0 : 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function usesLocalStorage(el?: Element | null) {
|
|
65
|
+
return Boolean(el?.hasAttribute('local-storage'));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getFormStoreKey(form?: HTMLFormElement | null) {
|
|
69
|
+
return form?.getAttribute('store-key') || '';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getElementStoreKey(el: Element) {
|
|
73
|
+
return el.getAttribute('store-key') || '';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getControlName(el: Element) {
|
|
77
|
+
return el.getAttribute('name') || ('name' in el && typeof el.name === 'string' ? el.name : '');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getPersistedControlKey(el: Element, form?: HTMLFormElement | null) {
|
|
81
|
+
const elementStoreKey = getElementStoreKey(el);
|
|
82
|
+
const formStoreKey = getFormStoreKey(form);
|
|
83
|
+
|
|
84
|
+
if (elementStoreKey) {
|
|
85
|
+
return formStoreKey ? `${formStoreKey}:${elementStoreKey}` : elementStoreKey;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const name = getControlName(el);
|
|
89
|
+
return formStoreKey && name ? `${formStoreKey}:${name}` : '';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getPersistedStore(el: Element, form?: HTMLFormElement | null) {
|
|
93
|
+
const localStorage = usesLocalStorage(el) || usesLocalStorage(form);
|
|
94
|
+
const ttl = getStoreTtl(el) || getStoreTtl(form);
|
|
95
|
+
return getStore(localStorage, ttl);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getStoredControlValue(el: Element, form?: HTMLFormElement | null): StoredFormControlValue | null {
|
|
99
|
+
const key = getPersistedControlKey(el, form);
|
|
100
|
+
const store = key ? getPersistedStore(el, form) : null;
|
|
101
|
+
const storedValue = store?.get(key);
|
|
102
|
+
|
|
103
|
+
if (!storedValue) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
return JSON.parse(storedValue) as StoredFormControlValue;
|
|
109
|
+
} catch {
|
|
110
|
+
return {value: storedValue};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function setStoredControlValue(el: Element, form: HTMLFormElement | null | undefined, value: StoredFormControlValue) {
|
|
115
|
+
const key = getPersistedControlKey(el, form);
|
|
116
|
+
const store = key ? getPersistedStore(el, form) : null;
|
|
117
|
+
|
|
118
|
+
if (!store) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
store.set(key, JSON.stringify(value));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isPersistableNativeControl(el: unknown): el is PersistableNativeControl {
|
|
126
|
+
return el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getNativeControlStoredValue(control: PersistableNativeControl): StoredFormControlValue {
|
|
130
|
+
if (control instanceof HTMLInputElement && (control.type === 'checkbox' || control.type === 'radio')) {
|
|
131
|
+
return {checked: control.checked, value: control.value};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {value: control.value};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function restoreNativeFormControls(form: HTMLFormElement) {
|
|
138
|
+
const controls = form.querySelectorAll<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>('input, textarea, select');
|
|
139
|
+
|
|
140
|
+
controls.forEach(control => {
|
|
141
|
+
const storedValue = getStoredControlValue(control, form);
|
|
142
|
+
if (!storedValue) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (control instanceof HTMLInputElement && (control.type === 'checkbox' || control.type === 'radio')) {
|
|
147
|
+
if (typeof storedValue.checked === 'boolean') {
|
|
148
|
+
control.checked = storedValue.checked;
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if ('value' in storedValue) {
|
|
154
|
+
control.value = String(storedValue.value ?? '');
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function persistNativeFormControl(target: EventTarget | null, form: HTMLFormElement) {
|
|
160
|
+
if (!isPersistableNativeControl(target) || target.form !== form) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
setStoredControlValue(target, form, getNativeControlStoredValue(target));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function getZincControlStoredValue(control: ZincFormControl, value: unknown): StoredFormControlValue {
|
|
168
|
+
const storedValue: StoredFormControlValue = {value};
|
|
169
|
+
|
|
170
|
+
if ('checked' in control && typeof control.checked === 'boolean') {
|
|
171
|
+
storedValue.checked = control.checked;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if ('indeterminate' in control && typeof control.indeterminate === 'boolean') {
|
|
175
|
+
storedValue.indeterminate = control.indeterminate;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return storedValue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function removeStoredValuesByPrefix(storage: Storage, keyPrefix: string) {
|
|
182
|
+
const keys: string[] = [];
|
|
183
|
+
const storageKey = formStorePrefix + keyPrefix;
|
|
184
|
+
|
|
185
|
+
for (let i = 0; i < storage.length; i++) {
|
|
186
|
+
const key = storage.key(i);
|
|
187
|
+
if (key === storageKey || key?.startsWith(storageKey + ':')) {
|
|
188
|
+
keys.push(key);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
keys.forEach(key => storage.removeItem(key));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function clearFormStoreValues(formOrStoreKey: HTMLFormElement | string) {
|
|
196
|
+
const keyPrefix = typeof formOrStoreKey === 'string' ? formOrStoreKey : getFormStoreKey(formOrStoreKey);
|
|
197
|
+
|
|
198
|
+
if (!keyPrefix) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const local = getStorage(true);
|
|
203
|
+
const session = getStorage(false);
|
|
204
|
+
|
|
205
|
+
if (local) {
|
|
206
|
+
removeStoredValuesByPrefix(local, keyPrefix);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (session && session !== local) {
|
|
210
|
+
removeStoredValuesByPrefix(session, keyPrefix);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
31
214
|
export interface FormControlControllerOptions {
|
|
32
215
|
/** A function that returns the form containing the form control. */
|
|
33
216
|
form: (input: ZincFormControl) => HTMLFormElement | null;
|
|
@@ -108,6 +291,7 @@ export class FormControlController implements ReactiveController {
|
|
|
108
291
|
});
|
|
109
292
|
|
|
110
293
|
await this.host.updateComplete;
|
|
294
|
+
this.restorePersistedValue();
|
|
111
295
|
if (form) {
|
|
112
296
|
this.checkFormValidity();
|
|
113
297
|
}
|
|
@@ -171,6 +355,8 @@ export class FormControlController implements ReactiveController {
|
|
|
171
355
|
this.form.addEventListener('cancelled', this.enableSubmit);
|
|
172
356
|
this.form.addEventListener('error', this.enableSubmit);
|
|
173
357
|
|
|
358
|
+
this.attachFormPersistence(this.form);
|
|
359
|
+
|
|
174
360
|
// Overload the form's reportValidity() method so it looks at Zinc form controls
|
|
175
361
|
if (!reportValidityOverloads.has(this.form)) {
|
|
176
362
|
reportValidityOverloads.set(this.form, this.form.reportValidity);
|
|
@@ -207,6 +393,7 @@ export class FormControlController implements ReactiveController {
|
|
|
207
393
|
this.form.removeEventListener('complete', this.enableSubmit);
|
|
208
394
|
this.form.removeEventListener('cancelled', this.enableSubmit);
|
|
209
395
|
this.form.removeEventListener('error', this.enableSubmit);
|
|
396
|
+
this.detachFormPersistence(this.form);
|
|
210
397
|
|
|
211
398
|
if (reportValidityOverloads.has(this.form)) {
|
|
212
399
|
this.form.reportValidity = reportValidityOverloads.get(this.form)!;
|
|
@@ -368,9 +555,56 @@ export class FormControlController implements ReactiveController {
|
|
|
368
555
|
if (emittedEvents.length === this.options.assumeInteractionOn.length) {
|
|
369
556
|
this.setUserInteracted(this.host, true);
|
|
370
557
|
}
|
|
558
|
+
this.persistHostValue();
|
|
371
559
|
this.checkFormValidity();
|
|
372
560
|
};
|
|
373
561
|
|
|
562
|
+
private attachFormPersistence(form: HTMLFormElement) {
|
|
563
|
+
if (formPersistenceListeners.has(form)) {
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const handleInput = (event: Event) => persistNativeFormControl(event.target, form);
|
|
568
|
+
const handleChange = (event: Event) => persistNativeFormControl(event.target, form);
|
|
569
|
+
form.addEventListener('input', handleInput);
|
|
570
|
+
form.addEventListener('change', handleChange);
|
|
571
|
+
formPersistenceListeners.set(form, {handleInput, handleChange});
|
|
572
|
+
|
|
573
|
+
restoreNativeFormControls(form);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
private detachFormPersistence(form: HTMLFormElement) {
|
|
577
|
+
const listeners = formPersistenceListeners.get(form);
|
|
578
|
+
if (!listeners) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
form.removeEventListener('input', listeners.handleInput);
|
|
583
|
+
form.removeEventListener('change', listeners.handleChange);
|
|
584
|
+
formPersistenceListeners.delete(form);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
private restorePersistedValue() {
|
|
588
|
+
const storedValue = getStoredControlValue(this.host, this.form);
|
|
589
|
+
if (!storedValue) {
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if ('checked' in this.host && typeof storedValue.checked === 'boolean') {
|
|
594
|
+
this.options.setValue(this.host, storedValue.checked);
|
|
595
|
+
} else if ('value' in storedValue) {
|
|
596
|
+
this.options.setValue(this.host, storedValue.value);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if ('indeterminate' in this.host && typeof storedValue.indeterminate === 'boolean') {
|
|
600
|
+
this.host.indeterminate = storedValue.indeterminate;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
private persistHostValue() {
|
|
605
|
+
setStoredControlValue(this.host, this.form, getZincControlStoredValue(this.host, this.options.value(this.host)));
|
|
606
|
+
}
|
|
607
|
+
|
|
374
608
|
private checkFormValidity = () => {
|
|
375
609
|
//
|
|
376
610
|
// This is very similar to the `reportFormValidity` function, but it does not trigger native constraint validation
|
package/src/internal/theme.ts
CHANGED
|
@@ -1,54 +1,34 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {signal, SignalWatcher} from '@lit-labs/signals';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
10
|
-
* ```ts
|
|
11
|
-
* import { ThemeController } from "@zinc/internal/theme";
|
|
12
|
-
*
|
|
13
|
-
* export default class MyElement extends HTMLElement {
|
|
14
|
-
* @property({ reflect: true }) t = '';
|
|
15
|
-
* ...
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
|
-
export class ThemeController implements ReactiveController {
|
|
19
|
-
host: ReactiveControllerHost & HTMLElement;
|
|
20
|
-
|
|
21
|
-
t: string = '';
|
|
3
|
+
function getDefaultTheme(): string {
|
|
4
|
+
if (typeof document === 'undefined') return 'light';
|
|
5
|
+
return document.documentElement.getAttribute('t')
|
|
6
|
+
|| document.body.getAttribute('t')
|
|
7
|
+
|| 'light';
|
|
8
|
+
}
|
|
22
9
|
|
|
23
|
-
|
|
24
|
-
this.host = host;
|
|
25
|
-
this.host.addController(this);
|
|
26
|
-
}
|
|
10
|
+
export const themeSignal = signal<string>(getDefaultTheme());
|
|
27
11
|
|
|
28
|
-
|
|
29
|
-
this.getDefaultTheme();
|
|
30
|
-
this.host.setAttribute('t', this.t);
|
|
31
|
-
window.addEventListener('theme-change', this.handleThemeEventUpdate.bind(this));
|
|
32
|
-
}
|
|
12
|
+
let listenerInstalled = false;
|
|
33
13
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
14
|
+
export function installThemeListener(): void {
|
|
15
|
+
if (listenerInstalled || typeof window === 'undefined') return;
|
|
16
|
+
listenerInstalled = true;
|
|
37
17
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
18
|
+
window.addEventListener('theme-change', (e: Event) => {
|
|
19
|
+
const detail: unknown = (e as CustomEvent<unknown>).detail;
|
|
20
|
+
let next: string;
|
|
21
|
+
if (typeof detail === 'string') {
|
|
22
|
+
next = detail;
|
|
23
|
+
} else if (detail && typeof detail === 'object' && 'theme' in detail && typeof (detail as {theme: unknown}).theme === 'string') {
|
|
24
|
+
next = (detail as {theme: string}).theme;
|
|
25
|
+
} else {
|
|
26
|
+
next = getDefaultTheme();
|
|
43
27
|
}
|
|
28
|
+
if (next !== themeSignal.get()) themeSignal.set(next);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
44
31
|
|
|
45
|
-
|
|
46
|
-
}
|
|
32
|
+
installThemeListener();
|
|
47
33
|
|
|
48
|
-
|
|
49
|
-
this.t = document.documentElement.getAttribute('t')
|
|
50
|
-
|| window.document.documentElement.getAttribute('t')
|
|
51
|
-
|| window.document.body.getAttribute('t')
|
|
52
|
-
|| 'light';
|
|
53
|
-
}
|
|
54
|
-
}
|
|
34
|
+
export {SignalWatcher};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {LitElement} from "lit";
|
|
1
|
+
import {LitElement, type PropertyValues} from "lit";
|
|
2
2
|
import {property} from "lit/decorators.js";
|
|
3
|
-
import {
|
|
3
|
+
import {SignalWatcher, themeSignal} from "./theme";
|
|
4
4
|
import type {
|
|
5
5
|
EventTypeDoesNotRequireDetail,
|
|
6
6
|
EventTypeRequiresDetail,
|
|
@@ -11,11 +11,19 @@ import type {
|
|
|
11
11
|
ZincEventInit
|
|
12
12
|
} from "./event";
|
|
13
13
|
|
|
14
|
-
export default class ZincElement extends LitElement {
|
|
14
|
+
export default class ZincElement extends SignalWatcher(LitElement) {
|
|
15
15
|
@property() dir: string; // LTR or RTL direction
|
|
16
16
|
@property() lang: string; // Language
|
|
17
17
|
@property({reflect: true}) t: string; // Theme (light or dark)
|
|
18
18
|
|
|
19
|
+
protected override willUpdate(changed: PropertyValues): void {
|
|
20
|
+
// Reading themeSignal here subscribes this component to theme changes
|
|
21
|
+
// via the SignalWatcher mixin. The reflected `t` attribute keeps existing
|
|
22
|
+
// CSS selectors like `[t="dark"]` working.
|
|
23
|
+
this.t = themeSignal.get();
|
|
24
|
+
super.willUpdate(changed);
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
static define(name: string, elementConstructor = this, options: ElementDefinitionOptions = {}) {
|
|
20
28
|
const currentRegisteredConstructor = customElements.get(name) as
|
|
21
29
|
| CustomElementConstructor
|
|
@@ -54,10 +62,6 @@ export default class ZincElement extends LitElement {
|
|
|
54
62
|
constructor() {
|
|
55
63
|
super();
|
|
56
64
|
|
|
57
|
-
// All Zinc components should have a theme controller /
|
|
58
|
-
/* eslint-disable no-new */
|
|
59
|
-
new ThemeController(this);
|
|
60
|
-
|
|
61
65
|
// Make sure we have access to all dependencies and they are defined
|
|
62
66
|
Object.entries((this.constructor as typeof ZincElement).dependencies).forEach(([name, component]) => {
|
|
63
67
|
(this.constructor as typeof ZincElement).define(name, component);
|
|
@@ -91,6 +95,7 @@ export interface ZincFormControl extends ZincElement {
|
|
|
91
95
|
defaultValue?: unknown;
|
|
92
96
|
defaultChecked?: boolean;
|
|
93
97
|
form?: string;
|
|
98
|
+
storeKey?: string;
|
|
94
99
|
|
|
95
100
|
// Constraint validation attributes
|
|
96
101
|
pattern?: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {clearFormStoreValues} from '../internal/form';
|
package/src/wc.scss
CHANGED
package/src/zinc.ts
CHANGED
|
@@ -26,6 +26,8 @@ export { default as Chart } from './components/chart';
|
|
|
26
26
|
export { default as SimpleChart } from './components/simple-chart';
|
|
27
27
|
export { default as Header } from './components/header';
|
|
28
28
|
export { default as Navbar } from './components/navbar';
|
|
29
|
+
export { default as Page } from './components/page';
|
|
30
|
+
export { default as Tab } from './components/tab';
|
|
29
31
|
export { default as IconPicker } from './components/icon-picker';
|
|
30
32
|
export { default as InlineEdit } from './components/inline-edit';
|
|
31
33
|
export { default as Pagination } from './components/pagination';
|
|
@@ -103,6 +105,7 @@ export { default as ZincElement } from './internal/zinc-element';
|
|
|
103
105
|
export * from './utilities/on';
|
|
104
106
|
export * from './utilities/query';
|
|
105
107
|
export * from './utilities/lit-to-html';
|
|
108
|
+
export * from './utilities/form';
|
|
106
109
|
|
|
107
110
|
// Events
|
|
108
111
|
export * from './events/events';
|