@kubex/zinc 1.0.113 → 1.0.115

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.
Files changed (101) hide show
  1. package/.github/workflows/eleventy_build.yml +5 -5
  2. package/.github/workflows/js_build_and_deploy.yaml +1 -1
  3. package/dist/custom-elements.json +2422 -190
  4. package/dist/vscode.html-custom-data.json +86 -5
  5. package/dist/web-types.json +251 -9
  6. package/dist/zn.d.ts +518 -340
  7. package/dist/zn.min.css +1 -1
  8. package/dist/zn.min.js +960 -882
  9. package/docs/eleventy.config.cjs +4 -2
  10. package/docs/pages/components/header.md +13 -0
  11. package/docs/pages/components/inline-edit.md +20 -0
  12. package/docs/pages/components/item.md +12 -1
  13. package/docs/pages/components/page.md +149 -0
  14. package/docs/pages/components/progress-bar.md +16 -0
  15. package/docs/pages/components/select.md +25 -0
  16. package/docs/pages/components/split-pane.md +6 -7
  17. package/docs/pages/components/tabs.md +59 -9
  18. package/package.json +8 -5
  19. package/scripts/build.js +40 -19
  20. package/scss/_global-spacing.scss +12 -1
  21. package/scss/_root.scss +34 -2
  22. package/scss/mixins/_spacing-mixins.scss +1 -1
  23. package/scss/shared/layout.scss +15 -2
  24. package/scss/themes/_light.scss +1 -1
  25. package/src/components/absolute-container/absolute-container.component.ts +9 -25
  26. package/src/components/animated-button/animated-button.test.ts +8 -8
  27. package/src/components/button/button.component.ts +3 -1
  28. package/src/components/button/button.scss +5 -0
  29. package/src/components/button-menu/button-menu.component.ts +36 -21
  30. package/src/components/button-menu/button-menu.test.ts +55 -1
  31. package/src/components/chart/chart.component.ts +7 -7
  32. package/src/components/collapsible/collapsible.component.ts +7 -14
  33. package/src/components/cols/cols.scss +1 -1
  34. package/src/components/content-block/content-block.component.ts +11 -33
  35. package/src/components/data-select/data-select.component.ts +7 -11
  36. package/src/components/data-select/data-select.scss +1 -0
  37. package/src/components/data-table/data-table.component.ts +10 -14
  38. package/src/components/dialog/dialog.scss +4 -0
  39. package/src/components/editor/modules/toolbar/toolbar.component.ts +5 -9
  40. package/src/components/expanding-action/expanding-action.component.ts +37 -43
  41. package/src/components/form-group/form-group.component.ts +32 -27
  42. package/src/components/form-group/form-group.scss +1 -0
  43. package/src/components/header/header.component.ts +6 -5
  44. package/src/components/header/header.scss +28 -26
  45. package/src/components/inline-edit/inline-edit.component.ts +13 -1
  46. package/src/components/inline-edit/inline-edit.test.ts +50 -0
  47. package/src/components/input/input.test.ts +77 -0
  48. package/src/components/input-group/input-group.test.ts +2 -10
  49. package/src/components/item/item.component.ts +12 -6
  50. package/src/components/item/item.scss +1 -1
  51. package/src/components/item/item.test.ts +8 -0
  52. package/src/components/navbar/navbar.component.ts +222 -48
  53. package/src/components/navbar/navbar.scss +134 -21
  54. package/src/components/navbar/navbar.test.ts +114 -9
  55. package/src/components/option/option.scss +5 -7
  56. package/src/components/option/option.test.ts +30 -0
  57. package/src/components/page/index.ts +13 -0
  58. package/src/components/page/page.component.ts +406 -0
  59. package/src/components/page/page.scss +325 -0
  60. package/src/components/page/page.test.ts +397 -0
  61. package/src/components/page-nav/page-nav.scss +7 -5
  62. package/src/components/pane/pane.component.ts +2 -2
  63. package/src/components/pane/pane.scss +0 -1
  64. package/src/components/pane/pane.test.ts +31 -0
  65. package/src/components/panel/panel.scss +5 -16
  66. package/src/components/progress-bar/progress-bar.component.ts +5 -2
  67. package/src/components/progress-bar/progress-bar.scss +17 -1
  68. package/src/components/progress-bar/progress-bar.test.ts +12 -0
  69. package/src/components/scroll-container/scroll-container.component.ts +22 -21
  70. package/src/components/select/select.component.ts +243 -39
  71. package/src/components/select/select.scss +36 -5
  72. package/src/components/select/select.test.ts +533 -0
  73. package/src/components/settings-container/settings-container.component.ts +15 -19
  74. package/src/components/settings-container/settings-container.scss +8 -8
  75. package/src/components/sidebar/sidebar.component.ts +11 -11
  76. package/src/components/simple-chart/simple-chart.component.ts +6 -7
  77. package/src/components/sp/sp.scss +46 -15
  78. package/src/components/sp/sp.test.ts +15 -0
  79. package/src/components/split-pane/split-pane.component.ts +153 -26
  80. package/src/components/split-pane/split-pane.scss +89 -13
  81. package/src/components/split-pane/split-pane.test.ts +187 -0
  82. package/src/components/style/style.component.ts +8 -0
  83. package/src/components/tab/index.ts +13 -0
  84. package/src/components/tab/tab.component.ts +25 -0
  85. package/src/components/tab/tab.scss +7 -0
  86. package/src/components/table/table.component.ts +7 -2
  87. package/src/components/table/table.scss +1 -0
  88. package/src/components/tabs/tabs.component.ts +75 -70
  89. package/src/components/tabs/tabs.scss +1 -1
  90. package/src/components/textarea/textarea.component.ts +5 -8
  91. package/src/components/tile/tile.scss +2 -2
  92. package/src/components/translation-group/translation-group.component.ts +15 -14
  93. package/src/components/translations/translations.component.ts +18 -17
  94. package/src/components/translations/translations.scss +1 -0
  95. package/src/components/translations/translations.test.ts +3 -2
  96. package/src/internal/form.ts +234 -0
  97. package/src/internal/theme.ts +26 -46
  98. package/src/internal/zinc-element.ts +12 -7
  99. package/src/utilities/form.ts +1 -0
  100. package/src/wc.scss +1 -1
  101. 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) {
@@ -30,6 +30,7 @@
30
30
 
31
31
  .form-control__label {
32
32
  margin-bottom: 0 !important;
33
+ white-space: nowrap;
33
34
  }
34
35
 
35
36
  .translations--flush,
@@ -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
- await expect(input!.getAttribute('value')).to.equal('Hello');
30
+ expect(input!.value).to.equal('Hello');
30
31
 
31
32
  // Simulate change
32
33
  input!.value = 'Hello World';
@@ -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
@@ -1,54 +1,34 @@
1
- import type {ReactiveController, ReactiveControllerHost} from "lit";
1
+ import {signal, SignalWatcher} from '@lit-labs/signals';
2
2
 
3
- /**
4
- * ThemeController is a reactive controller that listens for theme changes
5
- * and updates the theme property on the host element.
6
- *
7
- * if you want to reflect the theme attribute to the host element, you can use
8
- * the following code:
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
- constructor(host: ReactiveControllerHost & HTMLElement) {
24
- this.host = host;
25
- this.host.addController(this);
26
- }
10
+ export const themeSignal = signal<string>(getDefaultTheme());
27
11
 
28
- hostConnected() {
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
- hostDisconnected() {
35
- window.removeEventListener('theme-change', this.handleThemeEventUpdate.bind(this));
36
- }
14
+ export function installThemeListener(): void {
15
+ if (listenerInstalled || typeof window === 'undefined') return;
16
+ listenerInstalled = true;
37
17
 
38
- handleThemeEventUpdate = (e: CustomEvent & { theme: string }) => {
39
- if (e && e.detail) {
40
- this.t = e.detail as string;
41
- this.host.setAttribute('t', this.t);
42
- return;
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
- this.getDefaultTheme();
46
- }
32
+ installThemeListener();
47
33
 
48
- getDefaultTheme = () => {
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 {ThemeController} from "./theme";
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
@@ -28,7 +28,7 @@
28
28
  .width-container, .form-container, .wide-form-container {
29
29
  height: 100%;
30
30
  width: 100%;
31
- margin: 0 auto;
31
+ margin: var(--zn-default-margin, 0 auto);
32
32
  }
33
33
 
34
34
  .width-container {
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';