@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
@@ -0,0 +1,86 @@
1
+ @use "../../wc";
2
+
3
+ :host {
4
+ @include wc.scrollbars;
5
+
6
+ display: block;
7
+ width: 100%;
8
+ }
9
+
10
+ .thumbnail-group__header {
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: space-between;
14
+ gap: var(--zn-spacing-small);
15
+ margin-bottom: var(--zn-spacing-x-small);
16
+ }
17
+
18
+ .thumbnail-group__caption {
19
+ @include wc.text-style(h1);
20
+
21
+ min-width: 0;
22
+ text-overflow: ellipsis;
23
+ white-space: nowrap;
24
+ }
25
+
26
+ .thumbnail-group__header-actions {
27
+ display: flex;
28
+ align-items: center;
29
+ flex: 0 0 auto;
30
+ gap: var(--zn-spacing-x-small);
31
+ }
32
+
33
+ .thumbnail-group__toggle {
34
+ @include wc.text-style(subheading);
35
+
36
+ padding: 0;
37
+ background: none;
38
+ border: none;
39
+ cursor: pointer;
40
+ white-space: nowrap;
41
+
42
+ &:hover {
43
+ color: rgb(var(--zn-text));
44
+ }
45
+
46
+ &:focus-visible {
47
+ outline: var(--zn-focus-ring);
48
+ outline-offset: var(--zn-focus-ring-offset);
49
+ border-radius: var(--zn-border-radius-small);
50
+ }
51
+ }
52
+
53
+ /* Collapsed — one row, scrolled horizontally */
54
+
55
+ .thumbnail-group__items {
56
+ display: grid;
57
+ gap: var(--zn-thumbnail-gap, var(--zn-spacing-small));
58
+ grid-auto-flow: column;
59
+ grid-auto-columns: var(--zn-thumbnail-width, 180px);
60
+ overflow-x: auto;
61
+ overflow-y: hidden;
62
+ // Keeps the scrollbar off the captions.
63
+ padding-bottom: var(--zn-spacing-2x-small);
64
+ scroll-snap-type: x proximity;
65
+ overscroll-behavior-x: contain;
66
+ }
67
+
68
+ /* Expanded — wrapping grid, no row restriction */
69
+
70
+ .thumbnail-group--expanded .thumbnail-group__items {
71
+ grid-auto-flow: row;
72
+ grid-auto-columns: auto;
73
+ grid-template-columns: repeat(auto-fill, minmax(var(--zn-thumbnail-width, 180px), 1fr));
74
+ overflow: visible;
75
+ padding-bottom: 0;
76
+ }
77
+
78
+ // The slot has to disappear from the grid so the slotted thumbnails become the grid items.
79
+ slot {
80
+ display: contents;
81
+ }
82
+
83
+ ::slotted(*) {
84
+ scroll-snap-align: start;
85
+ min-width: 0;
86
+ }
@@ -0,0 +1,151 @@
1
+ import '../../../dist/zn.min.js';
2
+ import {expect, fixture, html, waitUntil} from '@open-wc/testing';
3
+ import type ZnThumbnail from '../thumbnail/thumbnail.component';
4
+ import type ZnThumbnailGroup from './thumbnail-group.component';
5
+
6
+ const thumbnails = (count: number) =>
7
+ Array.from({length: count}, (_, i) => `<zn-thumbnail value="v${i}" caption="Item ${i}"></zn-thumbnail>`).join('');
8
+
9
+ describe('<zn-thumbnail-group>', () => {
10
+ it('should render a component', async () => {
11
+ const el = await fixture(html`
12
+ <zn-thumbnail-group></zn-thumbnail-group>`);
13
+
14
+ expect(el).to.exist;
15
+ });
16
+
17
+ it('should render the caption', async () => {
18
+ const el = await fixture<ZnThumbnailGroup>(html`
19
+ <zn-thumbnail-group caption="Landscape"></zn-thumbnail-group>`);
20
+
21
+ expect(el.shadowRoot!.querySelector('.thumbnail-group__caption')!.textContent).to.contain('Landscape');
22
+ });
23
+
24
+ it('should not render the toggle when the thumbnails fit on one row', async () => {
25
+ const el = await fixture<ZnThumbnailGroup>(html`
26
+ <zn-thumbnail-group caption="Landscape" style="width: 800px;">
27
+ <zn-thumbnail caption="One"></zn-thumbnail>
28
+ <zn-thumbnail caption="Two"></zn-thumbnail>
29
+ </zn-thumbnail-group>`);
30
+
31
+ expect(el.shadowRoot!.querySelector('.thumbnail-group__toggle')).to.not.exist;
32
+ });
33
+
34
+ it('should render the toggle when the row overflows', async () => {
35
+ const el = await fixture<ZnThumbnailGroup>(
36
+ `<zn-thumbnail-group caption="Landscape" style="width: 400px;">${thumbnails(12)}</zn-thumbnail-group>`);
37
+
38
+ await waitUntil(() => el.shadowRoot!.querySelector('.thumbnail-group__toggle'));
39
+
40
+ const toggle = el.shadowRoot!.querySelector<HTMLButtonElement>('.thumbnail-group__toggle')!;
41
+ expect(toggle.textContent!.trim()).to.equal('Show All (12)');
42
+ });
43
+
44
+ it('should use the total attribute in the toggle label', async () => {
45
+ const el = await fixture<ZnThumbnailGroup>(html`
46
+ <zn-thumbnail-group caption="Landscape" total="79" style="width: 800px;">
47
+ <zn-thumbnail caption="One"></zn-thumbnail>
48
+ </zn-thumbnail-group>`);
49
+
50
+ const toggle = el.shadowRoot!.querySelector<HTMLButtonElement>('.thumbnail-group__toggle')!;
51
+ expect(toggle.textContent!.trim()).to.equal('Show All (79)');
52
+ });
53
+
54
+ it('should expand and collapse from the toggle', async () => {
55
+ const el = await fixture<ZnThumbnailGroup>(html`
56
+ <zn-thumbnail-group caption="Landscape" always-toggle>
57
+ <zn-thumbnail caption="One"></zn-thumbnail>
58
+ </zn-thumbnail-group>`);
59
+
60
+ const expanded: string[] = [];
61
+ el.addEventListener('zn-expand', () => expanded.push('expand'));
62
+ el.addEventListener('zn-collapse', () => expanded.push('collapse'));
63
+
64
+ el.shadowRoot!.querySelector<HTMLButtonElement>('.thumbnail-group__toggle')!.click();
65
+ await el.updateComplete;
66
+
67
+ expect(el.expanded).to.be.true;
68
+ expect(el.shadowRoot!.querySelector<HTMLButtonElement>('.thumbnail-group__toggle')!.textContent!.trim())
69
+ .to.equal('Show Less');
70
+
71
+ el.shadowRoot!.querySelector<HTMLButtonElement>('.thumbnail-group__toggle')!.click();
72
+ await el.updateComplete;
73
+
74
+ expect(el.expanded).to.be.false;
75
+ expect(expanded).to.deep.equal(['expand', 'collapse']);
76
+ });
77
+
78
+ it('should move selection between thumbnails when selectable', async () => {
79
+ const el = await fixture<ZnThumbnailGroup>(html`
80
+ <zn-thumbnail-group selectable>
81
+ <zn-thumbnail value="a" caption="A" selected></zn-thumbnail>
82
+ <zn-thumbnail value="b" caption="B"></zn-thumbnail>
83
+ </zn-thumbnail-group>`);
84
+
85
+ const [a, b] = Array.from(el.querySelectorAll<ZnThumbnail>('zn-thumbnail'));
86
+ expect(el.value).to.equal('a');
87
+
88
+ let changed = false;
89
+ el.addEventListener('zn-change', () => (changed = true));
90
+
91
+ b.shadowRoot!.querySelector<HTMLElement>('.thumbnail__link')!.click();
92
+ await el.updateComplete;
93
+
94
+ expect(el.value).to.equal('b');
95
+ expect(a.selected).to.be.false;
96
+ expect(b.selected).to.be.true;
97
+ expect(changed).to.be.true;
98
+ });
99
+
100
+ it('should mirror the shorthand attributes onto host custom properties', async () => {
101
+ const el = await fixture<ZnThumbnailGroup>(html`
102
+ <zn-thumbnail-group aspect-ratio="1 / 1" thumbnail-width="120px" gap="4px">
103
+ <zn-thumbnail caption="One"></zn-thumbnail>
104
+ </zn-thumbnail-group>`);
105
+
106
+ expect(el.style.getPropertyValue('--zn-thumbnail-aspect-ratio')).to.equal('1 / 1');
107
+ expect(el.style.getPropertyValue('--zn-thumbnail-width')).to.equal('120px');
108
+ expect(el.style.getPropertyValue('--zn-thumbnail-gap')).to.equal('4px');
109
+
110
+ // Thumbnails are light-DOM children, so they inherit the properties from the host.
111
+ const thumbnail = el.querySelector<ZnThumbnail>('zn-thumbnail')!;
112
+ const rect = thumbnail.shadowRoot!.querySelector<HTMLElement>('.thumbnail__media')!.getBoundingClientRect();
113
+ expect(rect.width).to.be.closeTo(120, 1);
114
+ expect(rect.height).to.be.closeTo(120, 1);
115
+ });
116
+
117
+ it('should not clear a custom property set inline by the consumer', async () => {
118
+ const el = await fixture<ZnThumbnailGroup>(html`
119
+ <zn-thumbnail-group style="--zn-thumbnail-width: 90px;">
120
+ <zn-thumbnail caption="One"></zn-thumbnail>
121
+ </zn-thumbnail-group>`);
122
+
123
+ expect(el.style.getPropertyValue('--zn-thumbnail-width')).to.equal('90px');
124
+ });
125
+
126
+ it('should clear a shorthand property once its attribute is removed', async () => {
127
+ const el = await fixture<ZnThumbnailGroup>(html`
128
+ <zn-thumbnail-group thumbnail-width="120px">
129
+ <zn-thumbnail caption="One"></zn-thumbnail>
130
+ </zn-thumbnail-group>`);
131
+
132
+ el.thumbnailWidth = '';
133
+ await el.updateComplete;
134
+
135
+ expect(el.style.getPropertyValue('--zn-thumbnail-width')).to.equal('');
136
+ });
137
+
138
+ it('should not manage selection when selectable is not set', async () => {
139
+ const el = await fixture<ZnThumbnailGroup>(html`
140
+ <zn-thumbnail-group>
141
+ <zn-thumbnail value="a" caption="A"></zn-thumbnail>
142
+ </zn-thumbnail-group>`);
143
+
144
+ const a = el.querySelector<ZnThumbnail>('zn-thumbnail')!;
145
+ a.shadowRoot!.querySelector<HTMLElement>('.thumbnail__link')!.click();
146
+ await el.updateComplete;
147
+
148
+ expect(a.selected).to.be.false;
149
+ expect(el.value).to.equal('');
150
+ });
151
+ });
@@ -10,6 +10,7 @@
10
10
  align-items: center;
11
11
  justify-content: space-between;
12
12
  min-height: 48px;
13
+ padding: var(--zn-base-gap);
13
14
  column-gap: var(--zn-spacing-large);
14
15
 
15
16
  font-size: var(--zn-text-table-content-font-size);
@@ -23,14 +24,15 @@
23
24
  &--has-properties,
24
25
  &--has-actions {
25
26
  padding-inline: var(--zn-base-gap);
27
+ min-height: 85px;
26
28
  }
27
29
 
28
30
  &--has-description#{&}--has-caption {
29
- padding-block: var(--zn-spacing-small);
31
+ padding-block: var(--zn-base-gap);
30
32
  }
31
33
 
32
34
  &--has-image {
33
- padding: var(--zn-spacing-small);
35
+ padding: var(--zn-base-gap);
34
36
  }
35
37
 
36
38
  &--has-href {
@@ -0,0 +1,22 @@
1
+ import '../../../dist/zn.min.js';
2
+ import { expect, fixture, html } from '@open-wc/testing';
3
+ import type ZnInlineEdit from '../inline-edit/inline-edit.component';
4
+ import type ZnTranslations from '../translations/translations.component';
5
+
6
+ describe('<zn-translation-group>', () => {
7
+ // The group syncs its children from its own first update, which runs before theirs, so a child's
8
+ // pending value attribute must survive the language keys the group adds.
9
+ it("keeps a child's rendered value when it syncs the group language", async () => {
10
+ const group = await fixture(html`
11
+ <zn-translation-group label="Text" .languages=${{en: 'English', fr: 'French'}}>
12
+ <zn-translations name="heading" value='{"en":"Chat to us"}'></zn-translations>
13
+ </zn-translation-group>`);
14
+
15
+ const child = group.querySelector<ZnTranslations>('zn-translations')!;
16
+ await child.updateComplete;
17
+
18
+ expect(child.values).to.deep.equal({en: 'Chat to us'});
19
+ const input = child.shadowRoot!.querySelector<ZnInlineEdit>('zn-inline-edit')!;
20
+ expect(input.value).to.equal('Chat to us');
21
+ });
22
+ });
@@ -150,15 +150,30 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
150
150
 
151
151
  /** Adds a language key to this component's values if not already present. Used by zn-translation-group. */
152
152
  public addLanguageKey(languageCode: string) {
153
- if (!Object.prototype.hasOwnProperty.call(this.values, languageCode)) {
154
- this.values = {...this.values, [languageCode]: ''};
153
+ const values = this.pendingValues();
154
+ if (!Object.prototype.hasOwnProperty.call(values, languageCode)) {
155
+ this.values = {...values, [languageCode]: ''};
155
156
  this.updateValue();
156
157
  }
157
158
  }
158
159
 
159
160
  /** Returns all language codes that have values. */
160
161
  public getValueLanguages(): string[] {
161
- return Object.keys(this.values);
162
+ return Object.keys(this.pendingValues());
163
+ }
164
+
165
+ /**
166
+ * `values`, falling back to the `value` attribute it is built from while that is still pending. A parent
167
+ * zn-translation-group syncs its children from its own first update, which runs before theirs, so reading
168
+ * `values` alone would see it empty and overwrite the value the server rendered.
169
+ */
170
+ private pendingValues(): Record<string, string> {
171
+ if (Object.keys(this.values).length > 0) return this.values;
172
+ try {
173
+ return JSON.parse(this.value || '{}') as Record<string, string>;
174
+ } catch {
175
+ return this.values;
176
+ }
162
177
  }
163
178
 
164
179
  disconnectedCallback() {
@@ -14,6 +14,7 @@ import styles from './well.scss';
14
14
  * @since 1.0
15
15
  *
16
16
  * @slot - The default slot.
17
+ * @slot action - Content displayed on the right hand side of the well.
17
18
  */
18
19
  export default class ZnWell extends ZincElement {
19
20
  static styles: CSSResultGroup = unsafeCSS(styles);
@@ -21,9 +22,21 @@ export default class ZnWell extends ZincElement {
21
22
  @property() icon: string = '';
22
23
  @property({attribute: 'inline', type: Boolean, reflect: true}) inline: boolean;
23
24
 
25
+ /** Renders the default slot inside a `pre` element, preserving whitespace using a monospace font. */
26
+ @property({attribute: 'pre', type: Boolean, reflect: true}) pre: boolean;
27
+
28
+ /** Breaks long unbroken words, so they wrap instead of forcing the well wider. */
29
+ @property({attribute: 'break-long', type: Boolean, reflect: true}) breakLong: boolean;
30
+
24
31
  private readonly hasSlotController = new HasSlotController(this, '[default]', 'action');
25
32
 
26
33
  render() {
34
+ const contentClasses = classMap({
35
+ 'well__content': true,
36
+ 'well__content--pre': this.pre,
37
+ 'well__content--break-long': this.breakLong,
38
+ });
39
+
27
40
  return html`
28
41
  <div class="${classMap({
29
42
  'well': true,
@@ -31,10 +44,15 @@ export default class ZnWell extends ZincElement {
31
44
  })}">
32
45
  ${this.icon ? html`
33
46
  <zn-icon src="${this.icon}" size="18"></zn-icon>` : ''}
34
- ${this.hasSlotController.test('[default]') ? html`
35
- <div>
36
- <slot></slot>
37
- </div>` : ''}
47
+ ${this.hasSlotController.test('[default]')
48
+ ? (this.pre
49
+ ? html`
50
+ <pre class="${contentClasses}"><slot></slot></pre>`
51
+ : html`
52
+ <div class="${contentClasses}">
53
+ <slot></slot>
54
+ </div>`)
55
+ : ''}
38
56
  ${this.hasSlotController.test('action') ? html`
39
57
  <slot name="action" class="well__action"></slot>` : ''}
40
58
  </div>
@@ -17,3 +17,27 @@
17
17
  .well--inline {
18
18
  display: inline-flex;
19
19
  }
20
+
21
+ // Without this the content's max-content width becomes the well's minimum size,
22
+ // so a single long unbroken string (a token, a URL) widens the whole well.
23
+ .well__content {
24
+ min-width: 0;
25
+ }
26
+
27
+ .well__content--pre {
28
+ margin: 0;
29
+ font-family: var(--zn-font-mono, 'SFMono-Regular', 'Consolas', 'Liberation Mono', 'Menlo', monospace);
30
+ white-space: pre;
31
+ overflow-x: auto;
32
+ }
33
+
34
+ .well__content--break-long {
35
+ overflow-wrap: anywhere;
36
+ word-break: break-word;
37
+ }
38
+
39
+ // Long lines have to be allowed to wrap before they can be broken.
40
+ .well__content--pre.well__content--break-long {
41
+ white-space: pre-wrap;
42
+ overflow-x: visible;
43
+ }
@@ -7,4 +7,47 @@ describe('<zn-well>', () => {
7
7
  <zn-well></zn-well> `);
8
8
  expect(el).to.exist;
9
9
  });
10
+
11
+ it('should render the default slot in a div by default', async () => {
12
+ const el = await fixture(html`
13
+ <zn-well>content</zn-well> `);
14
+ const content = el.shadowRoot!.querySelector('.well__content')!;
15
+ expect(content.tagName).to.equal('DIV');
16
+ expect(content.classList.contains('well__content--pre')).to.be.false;
17
+ });
18
+
19
+ it('should render the default slot in a pre when pre is set', async () => {
20
+ const el = await fixture(html`
21
+ <zn-well pre>content</zn-well> `);
22
+ const content = el.shadowRoot!.querySelector('.well__content')!;
23
+ expect(content.tagName).to.equal('PRE');
24
+ expect(getComputedStyle(content).whiteSpace).to.equal('pre');
25
+ });
26
+
27
+ it('should break long words when break-long is set', async () => {
28
+ const el = await fixture(html`
29
+ <zn-well break-long>content</zn-well> `);
30
+ const content = el.shadowRoot!.querySelector('.well__content')!;
31
+ expect(content.classList.contains('well__content--break-long')).to.be.true;
32
+ expect(getComputedStyle(content).overflowWrap).to.equal('anywhere');
33
+ });
34
+
35
+ it('should wrap preformatted content when pre and break-long are combined', async () => {
36
+ const el = await fixture(html`
37
+ <zn-well pre break-long>content</zn-well> `);
38
+ const content = el.shadowRoot!.querySelector('.well__content')!;
39
+ expect(content.tagName).to.equal('PRE');
40
+ expect(getComputedStyle(content).whiteSpace).to.equal('pre-wrap');
41
+ expect(getComputedStyle(content).overflowWrap).to.equal('anywhere');
42
+ });
43
+
44
+ it('should not let long unbroken content widen the well when break-long is set', async () => {
45
+ const token = 'a'.repeat(400);
46
+ const container = await fixture(html`
47
+ <div style="width: 300px">
48
+ <zn-well break-long>${token}</zn-well>
49
+ </div> `);
50
+ const el = container.querySelector('zn-well')!;
51
+ expect(el.getBoundingClientRect().width).to.be.at.most(300);
52
+ });
10
53
  });
@@ -123,6 +123,8 @@
123
123
  margin-top: var(--zn-spacing-3x-small);
124
124
  }
125
125
 
126
- .form-control--checkbox-contained-wrapper {
126
+ /* Let contained radios/checkboxes fill their grid row so cards in a row match height. */
127
+ .form-control--checkbox-contained-wrapper,
128
+ .form-control--radio-contained-wrapper {
127
129
  height: 100%;
128
130
  }
@@ -1,4 +1,4 @@
1
- import {deepQuerySelectorAll} from '../utilities/query';
1
+ import {deepQuerySelectorAll, idSelector} from '../utilities/query';
2
2
  import type {ReactiveController, ReactiveControllerHost} from 'lit';
3
3
 
4
4
  export interface ConditionalHost {
@@ -26,7 +26,7 @@ export class ConditionalController implements ReactiveController {
26
26
  const ids = this.host.conditional.split(',').map(id => id.trim());
27
27
 
28
28
  ids.forEach(id => {
29
- const byId = deepQuerySelectorAll(`#${id}`, document.documentElement, '') as (Element & {
29
+ const byId = deepQuerySelectorAll(idSelector(id), document.documentElement, '') as (Element & {
30
30
  value: string | string[];
31
31
  })[];
32
32
  const byName = deepQuerySelectorAll(`[name="${id}"]`, document.documentElement, '') as (Element & {
@@ -1,4 +1,5 @@
1
1
  import {getFormNavigationController} from "./form-navigation";
2
+ import {idSelector} from "../utilities/query";
2
3
  import {Store} from "./storage";
3
4
  import type {ReactiveController, ReactiveControllerHost} from "lit";
4
5
  import type {ZincFormControl} from "./zinc-element";
@@ -256,7 +257,7 @@ export class FormControlController implements ReactiveController {
256
257
 
257
258
  if (formId) {
258
259
  const root = input.getRootNode() as Document | ShadowRoot | HTMLElement;
259
- const form = root.querySelector(`#${formId}`);
260
+ const form = root.querySelector(idSelector(formId));
260
261
 
261
262
  if (form) {
262
263
  return form as HTMLFormElement;
@@ -0,0 +1,19 @@
1
+ /** Where a selection card renders its image, relative to the card's text. */
2
+ export type SelectionCardImagePosition = 'top' | 'right' | 'bottom' | 'left';
3
+
4
+ /**
5
+ * Where a selection card renders its radio/checkbox indicator. `start` keeps the standard
6
+ * inline position; `none` hides the indicator so the card itself shows the selected state.
7
+ */
8
+ export type SelectionCardControlPosition =
9
+ | 'start'
10
+ | 'none'
11
+ | 'top-left'
12
+ | 'top-center'
13
+ | 'top-right'
14
+ | 'center-left'
15
+ | 'center'
16
+ | 'center-right'
17
+ | 'bottom-left'
18
+ | 'bottom-center'
19
+ | 'bottom-right';