@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
@@ -0,0 +1,406 @@
1
+ import {classMap} from 'lit/directives/class-map.js';
2
+ import {type CSSResultGroup, html, type PropertyValues, unsafeCSS} from 'lit';
3
+ import {HasSlotController} from '../../internal/slot';
4
+ import {property, state} from 'lit/decorators.js';
5
+ import ZnButton from '../button';
6
+ import ZnCopyButton from '../copy-button';
7
+ import ZnExpandingAction from '../expanding-action';
8
+ import ZnIcon from '../icon';
9
+ import ZnNavbar from '../navbar';
10
+ import ZnTab from '../tab';
11
+ import ZnTabs from '../tabs';
12
+ import type {ZnSelectEvent} from '../../events/zn-select';
13
+
14
+ import styles from './page.scss';
15
+
16
+ interface TabDefinition {
17
+ id: string;
18
+ caption: string;
19
+ priority: number | null;
20
+ sourceIndex: number;
21
+ uri: string | null;
22
+ slotName: string | null;
23
+ }
24
+
25
+ /**
26
+ * @summary Combines a page header with tab navigation and tab panels.
27
+ * @documentation https://zinc.style/components/page
28
+ * @status experimental
29
+ * @since 1.0
30
+ *
31
+ * @slot - Page content. Use zn-tab for named tabs and header-action/header-actions for header actions.
32
+ * @slot bottom - Content rendered below the navbar row (e.g. chips, filters). Forwarded to the navbar's bottom slot.
33
+ */
34
+ export default class ZnPage extends ZnTabs {
35
+ static styles: CSSResultGroup = [ZnTabs.styles, unsafeCSS(styles)];
36
+ static dependencies = {
37
+ 'zn-button': ZnButton,
38
+ 'zn-copy-button': ZnCopyButton,
39
+ 'zn-expanding-action': ZnExpandingAction,
40
+ 'zn-icon': ZnIcon,
41
+ 'zn-navbar': ZnNavbar,
42
+ 'zn-tab': ZnTab
43
+ };
44
+
45
+ private readonly pageSlotController = new HasSlotController(this, 'breadcrumb', 'actions', 'caption');
46
+
47
+ @property() caption: string;
48
+ @property({attribute: 'entity-id'}) entityId: string;
49
+ @property({attribute: 'entity-id-show', type: Boolean}) entityIdShow: boolean;
50
+ @property({attribute: 'full-location'}) fullLocation: string;
51
+ @property({type: Boolean, reflect: true}) modal = false;
52
+ @property({type: Boolean, reflect: true}) nested = false;
53
+ @property({type: Boolean, reflect: true}) primary = false; // margin
54
+ @property({attribute: 'previous-path'}) previousPath: string;
55
+ @property({attribute: 'previous-target'}) previousTarget: string;
56
+ @property() summary: string;
57
+
58
+ @state() private scrolled = false;
59
+ @state() private tabDefinitions: TabDefinition[] = [];
60
+ @state() private hasExpandingActions = false;
61
+ private actionObserver: MutationObserver | null = null;
62
+ private tabObserver: MutationObserver | null = null;
63
+
64
+ async connectedCallback() {
65
+ const connected = super.connectedCallback();
66
+ window.addEventListener('alt-press', this.handleAltPress);
67
+ window.addEventListener('alt-up', this.handleAltUp);
68
+ this.prepareTabs();
69
+ this.refreshExpandingActionsState();
70
+ this.tabObserver = new MutationObserver((mutations) => {
71
+ if (mutations.some(mutation => mutation.type === 'childList')) {
72
+ this.prepareTabs();
73
+ }
74
+ });
75
+ this.tabObserver.observe(this, {childList: true});
76
+ this.actionObserver = new MutationObserver((mutations) => {
77
+ if (mutations.some(mutation => mutation.type === 'childList')) {
78
+ this.refreshExpandingActionsState();
79
+ this.syncExpandingActionsToNavbar();
80
+ }
81
+ });
82
+ this.actionObserver.observe(this, {childList: true, subtree: true});
83
+ await connected;
84
+ }
85
+
86
+ disconnectedCallback() {
87
+ super.disconnectedCallback();
88
+ window.removeEventListener('alt-press', this.handleAltPress);
89
+ window.removeEventListener('alt-up', this.handleAltUp);
90
+ this.tabObserver?.disconnect();
91
+ this.tabObserver = null;
92
+ this.actionObserver?.disconnect();
93
+ this.actionObserver = null;
94
+ }
95
+
96
+ private handleAltPress = () => {
97
+ this.classList.toggle('alt-pressed', true);
98
+ };
99
+
100
+ private handleAltUp = () => {
101
+ this.classList.toggle('alt-pressed', false);
102
+ };
103
+
104
+ _registerTabs = () => {
105
+ this.registerPageNavigationTabs();
106
+ };
107
+
108
+ firstUpdated(changedProperties: PropertyValues) {
109
+ super.firstUpdated(changedProperties);
110
+ this.registerPagePanels();
111
+ this.syncExpandingActionsToNavbar();
112
+ setTimeout(() => this.activateInitialPageTab(), 20);
113
+ }
114
+
115
+ private getOwnExpandingActions() {
116
+ return Array.from(this.querySelectorAll<ZnExpandingAction>('zn-expanding-action'))
117
+ .filter(action => action.closest('zn-page') === this);
118
+ }
119
+
120
+ private getNavbar() {
121
+ return this.shadowRoot?.querySelector<ZnNavbar>('zn-navbar') || null;
122
+ }
123
+
124
+ private refreshExpandingActionsState() {
125
+ const navbar = this.getNavbar();
126
+ const hasNavbarActions = Boolean(
127
+ navbar?.querySelector('zn-expanding-action') ||
128
+ navbar?.shadowRoot?.querySelector('zn-expanding-action')
129
+ );
130
+ const hasExpandingActions = this.getOwnExpandingActions().length > 0 || hasNavbarActions;
131
+
132
+ if (this.hasExpandingActions !== hasExpandingActions) {
133
+ this.hasExpandingActions = hasExpandingActions;
134
+ }
135
+ }
136
+
137
+ private syncExpandingActionsToNavbar() {
138
+ const navbar = this.getNavbar();
139
+ if (!navbar) {
140
+ return;
141
+ }
142
+
143
+ this.getOwnExpandingActions().forEach(action => navbar.addExpandingAction(action));
144
+ this.refreshExpandingActionsState();
145
+ }
146
+
147
+ private prepareTabs() {
148
+ const tabs: TabDefinition[] = [];
149
+ const usedIds = new Set<string>();
150
+ const tabElements = Array.from(this.children).filter((node): node is HTMLElement => node.tagName === 'ZN-TAB');
151
+
152
+ tabElements.forEach((tab, index) => {
153
+ const caption = tab.getAttribute('caption') || 'Tab';
154
+ const explicitId = tab.getAttribute('id');
155
+ const id = this.uniqueId(explicitId !== null ? explicitId : this.captionToId(caption), usedIds);
156
+ const priority = this.parsePriority(tab.getAttribute('priority'));
157
+ const uri = tab.getAttribute('uri');
158
+ const slotName = uri ? null : `page-tab-${index}`;
159
+
160
+ tab.id = id;
161
+
162
+ if (!uri) {
163
+ tab.slot = slotName!;
164
+ }
165
+
166
+ tabs.push({id, caption, priority, sourceIndex: index, uri, slotName});
167
+ });
168
+
169
+ this.tabDefinitions = tabs.sort((a, b) => this.compareTabs(a, b));
170
+ }
171
+
172
+ private parsePriority(value: string | null): number | null {
173
+ if (value === null || value.trim() === '') {
174
+ return null;
175
+ }
176
+
177
+ const priority = Number(value);
178
+ return Number.isFinite(priority) ? priority : null;
179
+ }
180
+
181
+ private compareTabs(a: TabDefinition, b: TabDefinition): number {
182
+ if (a.id === '' && b.id !== '') {
183
+ return -1;
184
+ }
185
+
186
+ if (a.id !== '' && b.id === '') {
187
+ return 1;
188
+ }
189
+
190
+ if (a.priority !== null && b.priority !== null && a.priority !== b.priority) {
191
+ return a.priority - b.priority;
192
+ }
193
+
194
+ if (a.priority !== null && b.priority === null) {
195
+ return -1;
196
+ }
197
+
198
+ if (a.priority === null && b.priority !== null) {
199
+ return 1;
200
+ }
201
+
202
+ return a.sourceIndex - b.sourceIndex;
203
+ }
204
+
205
+ private captionToId(caption: string): string {
206
+ if (caption.trim().toLowerCase() === 'overview') {
207
+ return '';
208
+ }
209
+
210
+ const id = caption.trim()
211
+ .toLowerCase()
212
+ .replace(/[^a-z0-9]+/g, '-')
213
+ .replace(/^-+|-+$/g, '');
214
+
215
+ return id || 'tab';
216
+ }
217
+
218
+ private uniqueId(id: string, usedIds: Set<string>): string {
219
+ let nextId = id;
220
+ let count = 2;
221
+
222
+ while (usedIds.has(nextId)) {
223
+ nextId = id === '' ? `tab-${count}` : `${id}-${count}`;
224
+ count += 1;
225
+ }
226
+
227
+ usedIds.add(nextId);
228
+ return nextId;
229
+ }
230
+
231
+ private handlePageScroll(event: Event) {
232
+ const scrolled = (event.currentTarget as HTMLElement).scrollTop > 24;
233
+ if (this.scrolled !== scrolled) {
234
+ this.scrolled = scrolled;
235
+ }
236
+ }
237
+
238
+ updated(changedProperties: PropertyValues) {
239
+ super.updated(changedProperties);
240
+
241
+ if (changedProperties.has('tabDefinitions')) {
242
+ this.registerPagePanels();
243
+ }
244
+
245
+ if (changedProperties.has('tabDefinitions') || changedProperties.has('hasExpandingActions')) {
246
+ this.syncExpandingActionsToNavbar();
247
+ }
248
+ }
249
+
250
+ private handleNavigationSelect(event: ZnSelectEvent) {
251
+ const item = event.detail.item as HTMLElement;
252
+ const tabUri = item.getAttribute('tab-uri');
253
+ const tabId = item.getAttribute('tab');
254
+
255
+ if (tabUri) {
256
+ this.clickTab(item, false);
257
+ this.syncNavigationActive(item);
258
+ return;
259
+ }
260
+
261
+ if (tabId !== null) {
262
+ this.activateTab(tabId, true);
263
+ }
264
+ }
265
+
266
+ private activateTab(tabId: string, store: boolean) {
267
+ this.setActiveTab(tabId, store, false);
268
+ const navItem = this.shadowRoot?.querySelector<HTMLElement>(`zn-navbar li[tab="${CSS.escape(tabId)}"]`);
269
+ if (navItem) {
270
+ this.syncNavigationActive(navItem);
271
+ }
272
+ }
273
+
274
+ private activateInitialPageTab() {
275
+ const selectedPanel = this.shadowRoot?.querySelector('#content > div[selected]');
276
+ const firstTab = this.tabDefinitions[0];
277
+
278
+ if (!selectedPanel && firstTab) {
279
+ this.activateTab(firstTab.id, false);
280
+ }
281
+ }
282
+
283
+ private registerPagePanels() {
284
+ this.shadowRoot?.querySelectorAll<HTMLElement>('#content > div[id]').forEach(panel => {
285
+ this._addPanel(panel);
286
+ });
287
+ this.registerPageNavigationTabs();
288
+ setTimeout(() => this.registerPageNavigationTabs());
289
+ }
290
+
291
+ private registerPageNavigationTabs() {
292
+ this.shadowRoot
293
+ ?.querySelectorAll<HTMLElement>('zn-navbar li[tab], zn-navbar li[tab-uri]')
294
+ .forEach(tab => this._addTab(tab));
295
+
296
+ this.shadowRoot
297
+ ?.querySelectorAll<ZnNavbar>('zn-navbar')
298
+ .forEach(navbar => {
299
+ navbar.shadowRoot
300
+ ?.querySelectorAll<HTMLElement>('li[tab], li[tab-uri]')
301
+ .forEach(tab => this._addTab(tab));
302
+ });
303
+ }
304
+
305
+ private syncNavigationActive(activeItem: HTMLElement) {
306
+ const navItems = this.shadowRoot?.querySelectorAll('zn-navbar li[tab], zn-navbar li[tab-uri]') || [];
307
+ navItems.forEach(item => {
308
+ const active = item === activeItem;
309
+ item.classList.toggle('active', active);
310
+ item.classList.toggle('zn-tb-active', active);
311
+ });
312
+ }
313
+
314
+ render() {
315
+ const hasBreadcrumb = !this.modal && !this.nested && this.pageSlotController.test('breadcrumb');
316
+ const hasNavigation = this.tabDefinitions.length > 1 || this.hasExpandingActions;
317
+ const hasEntityId = this.entityId;
318
+ const hasFullLocation = this.fullLocation;
319
+ const hasPreviousPath = this.previousPath;
320
+
321
+ return html`
322
+ <div class="page" part="base" @scroll="${this.handlePageScroll}">
323
+ <div class="${classMap({
324
+ 'page__header': true,
325
+ 'page__header--scrolled-no-navigation': this.scrolled && !hasNavigation
326
+ })}" part="header">
327
+ <div class="${classMap({
328
+ 'header': true,
329
+ 'header--has-breadcrumb': hasBreadcrumb,
330
+ 'header--has-entity-id': hasEntityId,
331
+ 'header--has-full-location': hasFullLocation,
332
+ 'header--has-navigation': hasNavigation,
333
+ 'header--has-previous': hasPreviousPath
334
+ })}">
335
+ ${hasFullLocation || hasEntityId ? html`
336
+ <div class="${classMap({
337
+ 'alt-overlay': true,
338
+ 'alt-overlay--visible': this.entityIdShow
339
+ })}">
340
+ ${hasFullLocation ? html`
341
+ <a href="${this.fullLocation}" target="_blank">
342
+ <zn-icon src="open_in_new"></zn-icon>
343
+ </a>` : null}
344
+ ${hasEntityId ? html`
345
+ <zn-copy-button copy-label="Copy Entity ID" value="${this.entityId}"
346
+ src="fingerprint"></zn-copy-button>`
347
+ : null}
348
+ ${hasFullLocation ? html`
349
+ <zn-copy-button copy-label="Copy Full Location" value="${this.fullLocation}"
350
+ src="link"></zn-copy-button>`
351
+ : null}
352
+ </div>` : null}
353
+
354
+ <div class="content" part="content">
355
+ ${hasPreviousPath ? html`
356
+ <a href="${this.previousPath}" class="caption__back"
357
+ data-target="${this.previousTarget ? this.previousTarget : ''}">
358
+ <zn-button size="content" icon="arrow_back" icon-size="24" color="transparent"></zn-button>
359
+ </a>` : null}
360
+
361
+ <div class="caption">
362
+ <div part="header-left" class="header__left">
363
+ <span class="header__caption" part="header-caption">
364
+ ${hasBreadcrumb ? html`
365
+ <slot name="breadcrumb" class="breadcrumb"></slot>` : null}
366
+ <slot name="caption">${this.caption}</slot>
367
+ </span>
368
+ ${this.summary ? html`
369
+ <span class="header__description" part="header-description">${this.summary}</span>`
370
+ : null}
371
+ </div>
372
+
373
+ <div class="header__right" part="header-right">
374
+ <slot name="actions"></slot>
375
+ </div>
376
+ </div>
377
+ </div>
378
+ </div>
379
+
380
+ ${hasNavigation ? html`
381
+ <zn-navbar hide-one @zn-select="${this.handleNavigationSelect}">
382
+ ${this.tabDefinitions.map(tab => tab.uri ? html`
383
+ <li tab-uri="${tab.uri}">${tab.caption}</li>
384
+ ` : html`
385
+ <li tab="${tab.id}">${tab.caption}</li>
386
+ `)}
387
+ <slot name="bottom" slot="bottom"></slot>
388
+ </zn-navbar>
389
+ ` : null}
390
+ </div>
391
+
392
+ <div class="page__tabs">
393
+ <div id="content" class="page__content" part="content">
394
+ ${this.tabDefinitions
395
+ .filter(tab => tab.slotName !== null)
396
+ .map(tab => html`
397
+ <div id="${tab.id}">
398
+ <slot name="${tab.slotName!}"></slot>
399
+ </div>
400
+ `)}
401
+ </div>
402
+ </div>
403
+ </div>
404
+ `;
405
+ }
406
+ }