@kubex/zinc 1.1.97 → 1.1.99

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 (85) hide show
  1. package/AGENTS.md +5 -0
  2. package/CLAUDE.md +5 -0
  3. package/dist/chunks/zn.R2EAU6OS.js +1 -0
  4. package/dist/custom-elements.json +14747 -12528
  5. package/dist/vscode.html-custom-data.json +386 -61
  6. package/dist/web-types.json +1232 -481
  7. package/dist/zn.d.ts +529 -11
  8. package/dist/zn.min.js +649 -478
  9. package/docs/pages/components/background.md +143 -0
  10. package/docs/pages/components/button.md +11 -1
  11. package/docs/pages/components/chart.md +165 -3
  12. package/docs/pages/components/checkbox-group.md +13 -1
  13. package/docs/pages/components/checkbox.md +59 -1
  14. package/docs/pages/components/icon-picker.md +8 -0
  15. package/docs/pages/components/radio-group.md +12 -0
  16. package/docs/pages/components/radio.md +188 -0
  17. package/docs/pages/components/remarkd-editor.md +22 -0
  18. package/docs/pages/components/scroll-container.md +14 -15
  19. package/docs/pages/components/thumbnail-group.md +216 -0
  20. package/docs/pages/components/thumbnail.md +349 -0
  21. package/docs/pages/components/well.md +34 -0
  22. package/package.json +1 -1
  23. package/src/components/background/background.component.ts +111 -0
  24. package/src/components/background/background.scss +236 -0
  25. package/src/components/background/background.test.ts +120 -0
  26. package/src/components/background/index.ts +12 -0
  27. package/src/components/button/button.component.ts +8 -0
  28. package/src/components/chart/builders.test.ts +190 -1
  29. package/src/components/chart/builders.ts +246 -4
  30. package/src/components/chart/chart.component.ts +33 -1
  31. package/src/components/chart/chart.test.ts +66 -1
  32. package/src/components/chart/echarts-loader.ts +10 -0
  33. package/src/components/checkbox/checkbox.component.ts +62 -6
  34. package/src/components/checkbox/checkbox.scss +1 -0
  35. package/src/components/checkbox/checkbox.test.ts +56 -0
  36. package/src/components/checkbox-group/checkbox-group.component.ts +15 -0
  37. package/src/components/checkbox-group/checkbox-group.scss +23 -5
  38. package/src/components/checkbox-group/checkbox-group.test.ts +15 -0
  39. package/src/components/collapsible/collapsible.component.ts +49 -4
  40. package/src/components/collapsible/collapsible.scss +33 -14
  41. package/src/components/confirm/confirm.component.ts +3 -3
  42. package/src/components/confirm/confirm.test.ts +15 -0
  43. package/src/components/dialog/dialog.component.ts +3 -2
  44. package/src/components/editor/editor.component.ts +7 -6
  45. package/src/components/header/header.scss +2 -2
  46. package/src/components/icon-picker/icon-picker.component.ts +3 -0
  47. package/src/components/icon-picker/lucide-icons.ts +1756 -0
  48. package/src/components/navbar/navbar.scss +11 -0
  49. package/src/components/page/page.scss +1 -1
  50. package/src/components/radio/radio.component.ts +63 -7
  51. package/src/components/radio/radio.scss +1 -0
  52. package/src/components/radio/radio.test.ts +56 -0
  53. package/src/components/radio-group/radio-group.component.ts +16 -3
  54. package/src/components/radio-group/radio-group.scss +23 -4
  55. package/src/components/radio-group/radio-group.test.ts +15 -0
  56. package/src/components/remarkd-editor/remarkd-editor.component.ts +118 -5
  57. package/src/components/remarkd-editor/remarkd-editor.scss +8 -1
  58. package/src/components/remarkd-editor/remarkd-editor.test.ts +135 -0
  59. package/src/components/scroll-container/scroll-container.component.ts +15 -1
  60. package/src/components/scroll-container/scroll-container.scss +4 -2
  61. package/src/components/scroll-container/scroll-container.test.ts +13 -0
  62. package/src/components/slideout/slideout.component.ts +3 -2
  63. package/src/components/split-pane/split-pane.scss +21 -15
  64. package/src/components/thumbnail/index.ts +12 -0
  65. package/src/components/thumbnail/thumbnail.component.ts +460 -0
  66. package/src/components/thumbnail/thumbnail.scss +360 -0
  67. package/src/components/thumbnail/thumbnail.test.ts +379 -0
  68. package/src/components/thumbnail-group/index.ts +12 -0
  69. package/src/components/thumbnail-group/thumbnail-group.component.ts +268 -0
  70. package/src/components/thumbnail-group/thumbnail-group.scss +86 -0
  71. package/src/components/thumbnail-group/thumbnail-group.test.ts +151 -0
  72. package/src/components/tile/tile.scss +4 -2
  73. package/src/components/translation-group/translation-group.test.ts +22 -0
  74. package/src/components/translations/translations.component.ts +18 -3
  75. package/src/components/well/well.component.ts +22 -4
  76. package/src/components/well/well.scss +24 -0
  77. package/src/components/well/well.test.ts +43 -0
  78. package/src/form-control.scss +3 -1
  79. package/src/internal/conditional.ts +2 -2
  80. package/src/internal/form.ts +2 -1
  81. package/src/internal/selection-card.ts +19 -0
  82. package/src/selection-card.scss +187 -0
  83. package/src/utilities/query.test.ts +18 -1
  84. package/src/utilities/query.ts +8 -0
  85. package/src/zinc.ts +3 -0
@@ -1,5 +1,6 @@
1
1
  import '../../../dist/zn.min.js';
2
2
  import {expect, fixture, html, waitUntil} from '@open-wc/testing';
3
+ import type ZnButton from '../button';
3
4
  import type ZnRemarkdEditor from './remarkd-editor.component';
4
5
  import type ZnSlashMenu from '../slash-menu';
5
6
 
@@ -79,6 +80,140 @@ Second"></zn-remarkd-editor>`);
79
80
  expect(el.shadowRoot!.querySelector('.remarkd-editor__toolbar')).to.exist;
80
81
  });
81
82
 
83
+ /** Every icon-only button needs a name of its own — it has no text for a screen reader to read. */
84
+ async function expectNamedIconButtons(el: ZnRemarkdEditor, root: ParentNode = el.shadowRoot!) {
85
+ const iconOnly = [...root.querySelectorAll<ZnButton>('zn-button')].filter(b => !b.textContent!.trim());
86
+ expect(iconOnly.length).to.be.greaterThan(0);
87
+
88
+ for (const button of iconOnly) {
89
+ await button.updateComplete;
90
+ const name = button.shadowRoot!.querySelector('button')!.getAttribute('aria-label');
91
+ expect(name, `the ${button.getAttribute('icon')} button has no accessible name`)
92
+ .to.be.a('string').and.not.empty;
93
+ }
94
+ }
95
+
96
+ it('should give every toolbar icon button an accessible name', async () => {
97
+ const el = await fixture<ZnRemarkdEditor>(html`
98
+ <zn-remarkd-editor allow-raw include-url="/includes"></zn-remarkd-editor>`);
99
+ const toolbar = el.shadowRoot!.querySelector('.remarkd-editor__toolbar')!;
100
+ expect(toolbar.querySelectorAll('zn-button').length).to.be.greaterThan(1);
101
+ await expectNamedIconButtons(el, toolbar);
102
+ // color-contrast is a pre-existing failure on the placeholder's shared
103
+ // --zn-color-neutral-400 token, unrelated to the buttons under test.
104
+ await expect(el).to.be.accessible({ignoredRules: ['color-contrast']});
105
+ });
106
+
107
+ it('should give every block action icon button an accessible name', async () => {
108
+ const el = await fixture<ZnRemarkdEditor>(html`
109
+ <zn-remarkd-editor value="# Title"></zn-remarkd-editor>`);
110
+ await expectNamedIconButtons(el, el.shadowRoot!.querySelector('.remarkd-editor__block')!);
111
+ });
112
+
113
+ it('should give every image control icon button an accessible name', async () => {
114
+ const el = await fixture<ZnRemarkdEditor>(html`
115
+ <zn-remarkd-editor value="image::photo.png[Alt,640,480]"></zn-remarkd-editor>`);
116
+ el.shadowRoot!.querySelector<HTMLElement>('.remarkd-editor__rendered')!.click();
117
+ await el.updateComplete;
118
+ await expectNamedIconButtons(el, el.shadowRoot!.querySelector('.remarkd-editor__image-controls')!);
119
+ });
120
+
121
+ /** A document tall enough to overflow the editor's max height. */
122
+ const longValue = Array.from({length: 60}, (_, i) => `Paragraph ${i}`).join('\n\n');
123
+
124
+ it('should not grow taller than the viewport', async () => {
125
+ const el = await fixture<ZnRemarkdEditor>(html`
126
+ <zn-remarkd-editor value=${longValue}></zn-remarkd-editor>`);
127
+ expect(el.getBoundingClientRect().height).to.be.at.most(window.innerHeight + 1);
128
+ });
129
+
130
+ /** Adds a block below the middle block via its gutter button. */
131
+ async function addBlockMidDocument(el: ZnRemarkdEditor) {
132
+ const blocks = el.shadowRoot!.querySelectorAll('.remarkd-editor__block');
133
+ blocks[Math.floor(blocks.length / 2)].querySelector('.remarkd-editor__actions zn-button')!
134
+ .dispatchEvent(new MouseEvent('click', {bubbles: true, composed: true, cancelable: true}));
135
+ await waitUntil(() => el.shadowRoot!.querySelector('.remarkd-editor__input'), 'no block opened');
136
+ }
137
+
138
+ it('should centre a newly added block in its own body', async () => {
139
+ const el = await fixture<ZnRemarkdEditor>(html`
140
+ <zn-remarkd-editor value=${longValue}></zn-remarkd-editor>`);
141
+ const body = el.shadowRoot!.querySelector('.remarkd-editor__body')!;
142
+ await addBlockMidDocument(el);
143
+
144
+ const bodyBox = body.getBoundingClientRect();
145
+ const inputBox = el.shadowRoot!.querySelector('.remarkd-editor__input')!.getBoundingClientRect();
146
+ expect(body.scrollTop, 'the body never scrolled').to.be.greaterThan(0);
147
+ expect(Math.abs((inputBox.top - bodyBox.top) - (bodyBox.bottom - inputBox.bottom)),
148
+ 'the new block is not centred').to.be.lessThan(60);
149
+ });
150
+
151
+ it('should not scroll an enclosing panel when a block is added', async () => {
152
+ // An `overflow: hidden` panel has no scrollbar but is still scrollable in code, so
153
+ // scrollIntoView() or a plain focus() would shift it with no way to scroll back.
154
+ const panel = await fixture<HTMLDivElement>(html`
155
+ <div style="height: 200px; overflow: hidden">
156
+ <zn-remarkd-editor value=${longValue}></zn-remarkd-editor>
157
+ <div style="height: 900px"></div>
158
+ </div>`);
159
+ const el = panel.querySelector<ZnRemarkdEditor>('zn-remarkd-editor')!;
160
+ await el.updateComplete;
161
+ const body = el.shadowRoot!.querySelector('.remarkd-editor__body')!;
162
+ await addBlockMidDocument(el);
163
+
164
+ expect(body.scrollTop, 'the editor body should still scroll').to.be.greaterThan(0);
165
+ expect(panel.scrollTop, 'the enclosing panel was scrolled').to.equal(0);
166
+ });
167
+
168
+ it('should not scroll an enclosing scroller when an image block is added', async () => {
169
+ const panel = await fixture<HTMLDivElement>(html`
170
+ <div style="height: 200px; overflow: auto">
171
+ <zn-remarkd-editor value=${longValue}></zn-remarkd-editor>
172
+ <div style="height: 900px"></div>
173
+ </div>`);
174
+ const el = panel.querySelector<ZnRemarkdEditor>('zn-remarkd-editor')!;
175
+ await el.updateComplete;
176
+ const body = el.shadowRoot!.querySelector('.remarkd-editor__body')!;
177
+ expect(panel.scrollHeight - panel.clientHeight, 'the panel needs room to scroll')
178
+ .to.be.greaterThan(0);
179
+
180
+ // A 1x1 gif: an image block whose height only lands once it loads.
181
+ const src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
182
+ el.value = `${longValue}\n\nimage::${src}[pixel]`;
183
+ await el.updateComplete;
184
+ await waitUntil(() => el.shadowRoot!.querySelector('.remarkd-editor__block img'), 'no image block');
185
+
186
+ expect(panel.scrollTop, 'the enclosing scroller was scrolled').to.equal(0);
187
+ expect(body.scrollTop, 'the body should be scrollable').to.be.at.least(0);
188
+ });
189
+
190
+ it('should keep scrolling while a drag is held at the edge', async () => {
191
+ const el = await fixture<ZnRemarkdEditor>(html`
192
+ <zn-remarkd-editor value=${longValue}></zn-remarkd-editor>`);
193
+ const body = el.shadowRoot!.querySelector('.remarkd-editor__body')!;
194
+ const handle = el.shadowRoot!.querySelectorAll('.remarkd-editor__block')[1]
195
+ .querySelector('.remarkd-editor__drag-handle')!;
196
+ const from = handle.getBoundingClientRect();
197
+ const drag = (type: string, clientY: number, target: EventTarget, buttons = 1) =>
198
+ target.dispatchEvent(new PointerEvent(type, {
199
+ bubbles: true, composed: true, cancelable: true,
200
+ clientX: Math.round(from.left + 5), clientY, button: 0, buttons,
201
+ }));
202
+
203
+ drag('pointerdown', Math.round(from.top + 5), handle);
204
+ drag('pointermove', Math.round(from.top + 20), document);
205
+ expect(el.shadowRoot!.querySelector('.remarkd-editor__ghost'), 'the drag never started').to.exist;
206
+
207
+ // Park the pointer in the bottom edge zone — the frame loop scrolls without further moves.
208
+ drag('pointermove', Math.round(body.getBoundingClientRect().bottom - 20), document);
209
+ await waitUntil(() => body.scrollTop > 0, 'a drag held at the edge never scrolled');
210
+
211
+ drag('pointercancel', 0, document, 0);
212
+ const settled = body.scrollTop;
213
+ await new Promise(resolve => setTimeout(resolve, 100));
214
+ expect(body.scrollTop, 'scrolling continued after the drag ended').to.equal(settled);
215
+ });
216
+
82
217
  it('should open zn-slash-menu with the block types when "/" starts an empty block', async () => {
83
218
  const el = await fixture<ZnRemarkdEditor>(html`
84
219
  <zn-remarkd-editor value="Hello"></zn-remarkd-editor>`);
@@ -21,11 +21,14 @@ import styles from './scroll-container.scss';
21
21
  *
22
22
  * @csspart base - The component's base wrapper.
23
23
  *
24
- * @cssproperty --example - An example CSS custom property.
24
+ * @cssproperty --zn-scroll-container-height - The height and maximum height of the scroll container.
25
25
  */
26
26
  export default class ZnScrollContainer extends ZincElement {
27
27
  static styles: CSSResultGroup = unsafeCSS(styles);
28
28
 
29
+ /** The height and maximum height of the scroll container. */
30
+ @property({type: String}) height = '';
31
+
29
32
  @property({attribute: 'start-scrolled', type: Boolean, reflect: true}) startScrolled: boolean = false;
30
33
 
31
34
  @query('.scroll-container')
@@ -44,6 +47,17 @@ export default class ZnScrollContainer extends ZincElement {
44
47
  }
45
48
  }
46
49
 
50
+ protected updated(changedProperties: PropertyValues) {
51
+ super.updated(changedProperties);
52
+ if (changedProperties.has('height')) {
53
+ if (this.height) {
54
+ this.style.setProperty('--zn-scroll-container-height-property', this.height);
55
+ } else {
56
+ this.style.removeProperty('--zn-scroll-container-height-property');
57
+ }
58
+ }
59
+ }
60
+
47
61
  disconnectedCallback() {
48
62
  super.disconnectedCallback();
49
63
  if (this._footerHeightFrame !== null) {
@@ -2,8 +2,10 @@
2
2
 
3
3
  :host {
4
4
  @include wc.scrollbars;
5
-
6
- display: contents;
5
+
6
+ display: block;
7
+ height: var(--zn-scroll-container-height-property, var(--zn-scroll-container-height, 100%));
8
+ max-height: var(--zn-scroll-container-height-property, var(--zn-scroll-container-height, 100%));
7
9
  --zn-scroll-footer-height: 0px;
8
10
  --zn-scroll-header-height: 0px;
9
11
  }
@@ -7,4 +7,17 @@ describe('<zn-scroll-container>', () => {
7
7
 
8
8
  expect(el).to.exist;
9
9
  });
10
+
11
+ it('should render as a block-level container', async () => {
12
+ const el = await fixture(html` <zn-scroll-container></zn-scroll-container> `);
13
+
14
+ expect(getComputedStyle(el).display).to.equal('block');
15
+ });
16
+
17
+ it('should apply height as the height and maximum height', async () => {
18
+ const el = await fixture(html` <zn-scroll-container height="240px"></zn-scroll-container> `);
19
+
20
+ expect(getComputedStyle(el).height).to.equal('240px');
21
+ expect(getComputedStyle(el).maxHeight).to.equal('240px');
22
+ });
10
23
  });
@@ -4,6 +4,7 @@ import {type CSSResultGroup, html, type PropertyValues, unsafeCSS} from 'lit';
4
4
  import {LocalizeController} from "../../utilities/localize";
5
5
  import {property, query} from 'lit/decorators.js';
6
6
  import {getAnimation, setDefaultAnimation} from "../../utilities/animation-registry";
7
+ import {idSelector} from "../../utilities/query";
7
8
  import {unlockBodyScrolling} from "../../internal/scroll";
8
9
  import ZincElement from '../../internal/zinc-element';
9
10
  import ZnButton from "../button";
@@ -83,7 +84,7 @@ export default class ZnSlideout extends ZincElement {
83
84
  this.shadowRoot?.addEventListener('click', this.closeClickHandler);
84
85
 
85
86
  if (this.trigger) {
86
- const trigger = this.parentNode?.querySelector('#' + this.trigger);
87
+ const trigger = this.parentNode?.querySelector(idSelector(this.trigger));
87
88
  if (trigger) {
88
89
  trigger.addEventListener('click', () => this.show());
89
90
  }
@@ -98,7 +99,7 @@ export default class ZnSlideout extends ZincElement {
98
99
  this.removeEventListener('click', this.closeClickHandler);
99
100
 
100
101
  if (this.trigger) {
101
- const trigger = this.parentElement?.querySelector('#' + this.trigger);
102
+ const trigger = this.parentElement?.querySelector(idSelector(this.trigger));
102
103
  if (trigger) {
103
104
  trigger.removeEventListener('click', () => this.show());
104
105
  }
@@ -24,9 +24,20 @@
24
24
  container-type: inline-size;
25
25
  position: relative;
26
26
 
27
- #primary-pane, #secondary-pane {
28
- flex-grow: 1;
29
- flex-shrink: 1;
27
+ // Panes are sized from --initial-size, never from their content. Without an
28
+ // explicit flex-basis, a wide unbreakable child (long token in a <pre>, wide
29
+ // table) becomes the flex base size and the resizer stops having any effect.
30
+ #primary-pane {
31
+ flex: 0 1 var(--initial-size, 50%);
32
+ min-width: 0;
33
+ min-height: 0;
34
+ }
35
+
36
+ #secondary-pane {
37
+ flex: 1 1 0;
38
+ min-width: 0;
39
+ min-height: 0;
40
+ overflow: hidden;
30
41
  }
31
42
 
32
43
  slot {
@@ -116,6 +127,7 @@
116
127
  }
117
128
 
118
129
  #primary-pane, #secondary-pane {
130
+ flex: 1 1 100%;
119
131
  max-width: 100% !important;
120
132
  width: 100%;
121
133
  padding-top: 40px;
@@ -131,12 +143,8 @@
131
143
  }
132
144
 
133
145
  #primary-pane {
134
- max-width: var(--max-panel-size, var(--initial-size, 50%));
135
- }
136
-
137
- #secondary-pane {
138
- max-width: calc(100% - var(--initial-size, 0));
139
- width: 100%;
146
+ max-width: var(--max-panel-size, none);
147
+ overflow: hidden;
140
148
  }
141
149
 
142
150
  #resizer {
@@ -199,6 +207,7 @@
199
207
  }
200
208
 
201
209
  #secondary-pane {
210
+ flex: 1 1 100%;
202
211
  height: 100%;
203
212
  max-height: 100% !important;
204
213
  max-width: 100% !important;
@@ -208,6 +217,7 @@
208
217
 
209
218
  :host([hide="secondary"]) {
210
219
  #primary-pane {
220
+ flex: 1 1 100%;
211
221
  height: 100%;
212
222
  max-height: 100% !important;
213
223
  max-width: 100% !important;
@@ -258,12 +268,8 @@
258
268
  }
259
269
 
260
270
  #primary-pane {
261
- max-height: var(--initial-size, 50%);
262
- }
263
-
264
- #secondary-pane {
265
- height: 100%;
266
- max-height: calc(100% - var(--initial-size, 0));
271
+ max-height: var(--max-panel-size, none);
272
+ overflow: hidden;
267
273
  }
268
274
 
269
275
  #resizer {
@@ -0,0 +1,12 @@
1
+ import ZnThumbnail from './thumbnail.component';
2
+
3
+ export * from './thumbnail.component';
4
+ export default ZnThumbnail;
5
+
6
+ ZnThumbnail.define('zn-thumbnail');
7
+
8
+ declare global {
9
+ interface HTMLElementTagNameMap {
10
+ 'zn-thumbnail': ZnThumbnail;
11
+ }
12
+ }