@kubex/zinc 1.1.98 → 1.1.100
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/AGENTS.md +5 -0
- package/CLAUDE.md +5 -0
- package/dist/custom-elements.json +876 -18
- package/dist/vscode.html-custom-data.json +148 -9
- package/dist/web-types.json +306 -12
- package/dist/zn.d.ts +203 -9
- package/dist/zn.min.css +1 -1
- package/dist/zn.min.js +343 -297
- package/docs/pages/components/background.md +143 -0
- package/docs/pages/components/checkbox-group.md +13 -1
- package/docs/pages/components/checkbox.md +59 -1
- package/docs/pages/components/radio-group.md +12 -0
- package/docs/pages/components/radio.md +188 -0
- package/docs/pages/components/remarkd-editor.md +22 -0
- package/docs/pages/components/scroll-container.md +14 -15
- package/package.json +1 -1
- package/scss/_root.scss +16 -0
- package/scss/mixins/_typography-mixins.scss +1 -0
- package/scss/shared/_button.scss +4 -2
- package/scss/themes/_typography.scss +2 -0
- package/src/components/background/background.component.ts +111 -0
- package/src/components/background/background.scss +236 -0
- package/src/components/background/background.test.ts +120 -0
- package/src/components/background/index.ts +12 -0
- package/src/components/button/button.scss +14 -4
- package/src/components/checkbox/checkbox.component.ts +62 -6
- package/src/components/checkbox/checkbox.scss +1 -0
- package/src/components/checkbox/checkbox.test.ts +56 -0
- package/src/components/checkbox-group/checkbox-group.component.ts +15 -0
- package/src/components/checkbox-group/checkbox-group.scss +23 -5
- package/src/components/checkbox-group/checkbox-group.test.ts +15 -0
- package/src/components/confirm/confirm.component.ts +3 -3
- package/src/components/confirm/confirm.test.ts +15 -0
- package/src/components/dialog/dialog.component.ts +3 -2
- package/src/components/editor/editor.component.ts +7 -6
- package/src/components/header/header.scss +3 -1
- package/src/components/menu-item/menu-item.scss +8 -5
- package/src/components/navbar/navbar.scss +11 -0
- package/src/components/page/page.scss +16 -2
- package/src/components/radio/radio.component.ts +63 -7
- package/src/components/radio/radio.scss +1 -0
- package/src/components/radio/radio.test.ts +56 -0
- package/src/components/radio-group/radio-group.component.ts +16 -3
- package/src/components/radio-group/radio-group.scss +23 -4
- package/src/components/radio-group/radio-group.test.ts +15 -0
- package/src/components/remarkd-editor/remarkd-editor.component.ts +118 -5
- package/src/components/remarkd-editor/remarkd-editor.scss +8 -1
- package/src/components/remarkd-editor/remarkd-editor.test.ts +96 -0
- package/src/components/scroll-container/scroll-container.component.ts +15 -1
- package/src/components/scroll-container/scroll-container.scss +4 -2
- package/src/components/scroll-container/scroll-container.test.ts +13 -0
- package/src/components/slideout/slideout.component.ts +3 -2
- package/src/components/thumbnail-group/thumbnail-group.scss +0 -1
- package/src/components/tile/tile.scss +2 -2
- package/src/components/toggle/toggle.scss +8 -1
- package/src/components/tooltip/tooltip.scss +4 -1
- package/src/components/translation-group/translation-group.test.ts +22 -0
- package/src/components/translations/translations.component.ts +18 -3
- package/src/form-control.scss +3 -1
- package/src/internal/conditional.ts +2 -2
- package/src/internal/form.ts +2 -1
- package/src/internal/selection-card.ts +19 -0
- package/src/selection-card.scss +187 -0
- package/src/utilities/query.test.ts +18 -1
- package/src/utilities/query.ts +8 -0
- package/src/zinc.ts +1 -0
|
@@ -9,6 +9,7 @@ import {property, query, state} from 'lit/decorators.js';
|
|
|
9
9
|
import {watch} from '../../internal/watch';
|
|
10
10
|
import ZincElement from '../../internal/zinc-element';
|
|
11
11
|
import ZnIcon from "../icon";
|
|
12
|
+
import type {SelectionCardControlPosition, SelectionCardImagePosition} from '../../internal/selection-card';
|
|
12
13
|
import type {ZincFormControl} from '../../internal/zinc-element';
|
|
13
14
|
|
|
14
15
|
import styles from './checkbox.scss';
|
|
@@ -24,6 +25,7 @@ type ColorOption = 'default' | 'primary' | 'secondary' | 'error' | 'info' | 'suc
|
|
|
24
25
|
* @dependency zn-icon
|
|
25
26
|
*
|
|
26
27
|
* @slot - The checkbox's label.
|
|
28
|
+
* @slot image - Replaces the built-in image used by contained checkboxes.
|
|
27
29
|
* @slot description - A description of the checkbox's label. Serves as help text for a checkbox item. Alternatively, you can use the `description` attribute.
|
|
28
30
|
* @slot selected-content - Use to nest rich content (like an input) inside a selected checkbox item. Use only with the contained style.
|
|
29
31
|
*
|
|
@@ -40,9 +42,22 @@ type ColorOption = 'default' | 'primary' | 'secondary' | 'error' | 'info' | 'suc
|
|
|
40
42
|
* @csspart checked-icon - The checked icon, an `<zn-icon>` element.
|
|
41
43
|
* @csspart unchecked-icon - The unchecked icon, an `<zn-icon>` element.
|
|
42
44
|
* @csspart indeterminate-icon - The indeterminate icon, an `<zn-icon>` element.
|
|
45
|
+
* @csspart image-container - The wrapper around the built-in image or image slot.
|
|
46
|
+
* @csspart image - The built-in image.
|
|
47
|
+
* @csspart card-title - The title inside a selection card.
|
|
43
48
|
* @csspart label - The container that wraps the checkbox's label.
|
|
44
49
|
* @csspart description - The container that wraps the checkbox's description.
|
|
45
50
|
* @csspart selected-content - The container that wraps optional content that appears when a checkbox is checked.
|
|
51
|
+
*
|
|
52
|
+
* @cssproperty --zn-selection-card-image-width - Width of the built-in card image.
|
|
53
|
+
* @cssproperty --zn-selection-card-image-height - Height of the built-in card image.
|
|
54
|
+
* @cssproperty --zn-selection-card-min-height - Minimum height of a contained checkbox with an image.
|
|
55
|
+
* @cssproperty --zn-selection-card-content-min-width - Width the card text keeps before it wraps below the image.
|
|
56
|
+
* @cssproperty --zn-selection-card-title-font-size - Font size of a selection card title.
|
|
57
|
+
* @cssproperty --zn-selection-card-padding - Equal inset around the contents of a selection card.
|
|
58
|
+
* @cssproperty --zn-selection-card-gap - Space between the image, title, and indicator gutter.
|
|
59
|
+
* @cssproperty --zn-selection-card-control-offset - Distance between a positioned control and the card edge.
|
|
60
|
+
* @cssproperty --zn-selection-card-border-radius - Corner radius of a contained container.
|
|
46
61
|
*/
|
|
47
62
|
export default class ZnCheckbox extends ZincElement implements ZincFormControl {
|
|
48
63
|
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
@@ -53,7 +68,7 @@ export default class ZnCheckbox extends ZincElement implements ZincFormControl {
|
|
|
53
68
|
defaultValue: (control: ZnCheckbox) => control.defaultChecked,
|
|
54
69
|
setValue: (control: ZnCheckbox, checked: boolean) => (control.checked = checked)
|
|
55
70
|
});
|
|
56
|
-
private readonly hasSlotController = new HasSlotController(this, 'description');
|
|
71
|
+
private readonly hasSlotController = new HasSlotController(this, '[default]', 'description', 'image');
|
|
57
72
|
|
|
58
73
|
@query('input[type="checkbox"]') input: HTMLInputElement;
|
|
59
74
|
|
|
@@ -67,6 +82,9 @@ export default class ZnCheckbox extends ZincElement implements ZincFormControl {
|
|
|
67
82
|
/** The current value of the checkbox, submitted as a name/value pair with form data. */
|
|
68
83
|
@property() value: string;
|
|
69
84
|
|
|
85
|
+
/** Title rendered inside the card. The default slot takes precedence when provided. */
|
|
86
|
+
@property({attribute: 'card-title'}) cardTitle = '';
|
|
87
|
+
|
|
70
88
|
/** The unchecked value of the checkbox, submitted as a name/value pair with form data. */
|
|
71
89
|
@property({attribute: 'unchecked-value', reflect: true}) uncheckedValue: string;
|
|
72
90
|
|
|
@@ -88,6 +106,24 @@ export default class ZnCheckbox extends ZincElement implements ZincFormControl {
|
|
|
88
106
|
/** Draws a container around the checkbox. */
|
|
89
107
|
@property({type: Boolean, reflect: true}) contained = false;
|
|
90
108
|
|
|
109
|
+
/** Squares off the corners of the container drawn by `contained`, which is rounded by default. */
|
|
110
|
+
@property({type: Boolean, reflect: true}) square = false;
|
|
111
|
+
|
|
112
|
+
/** URL for the image shown in a contained checkbox. */
|
|
113
|
+
@property() src = '';
|
|
114
|
+
|
|
115
|
+
/** Accessible text for the built-in image. Leave empty when the image is decorative. */
|
|
116
|
+
@property({attribute: 'image-alt'}) imageAlt = '';
|
|
117
|
+
|
|
118
|
+
/** Places the image above, beside, or below the checkbox's text. Requires `contained`. */
|
|
119
|
+
@property({attribute: 'image-position', reflect: true}) imagePosition: SelectionCardImagePosition = 'left';
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Places the checkbox indicator within a contained card. Use `none` to hide the indicator so the card itself
|
|
123
|
+
* shows the selected state. Requires `contained`.
|
|
124
|
+
*/
|
|
125
|
+
@property({attribute: 'control-position', reflect: true}) controlPosition: SelectionCardControlPosition = 'start';
|
|
126
|
+
|
|
91
127
|
/** Removes a container around the checkbox. */
|
|
92
128
|
@property({type: Boolean, reflect: true}) borderless = false;
|
|
93
129
|
|
|
@@ -244,6 +280,9 @@ export default class ZnCheckbox extends ZincElement implements ZincFormControl {
|
|
|
244
280
|
render() {
|
|
245
281
|
const hasDescriptionSlot = this.hasSlotController.test('description');
|
|
246
282
|
const hasDescription = this.description ? true : hasDescriptionSlot;
|
|
283
|
+
const hasImage = this.contained && (Boolean(this.src) || this.hasSlotController.test('image'));
|
|
284
|
+
const hasDefaultSlot = this.hasSlotController.test('[default]');
|
|
285
|
+
const isCard = this.contained && (hasImage || Boolean(this.cardTitle) || this.controlPosition !== 'start');
|
|
247
286
|
const hasLabelSlot = this.hasSlotController.test('label');
|
|
248
287
|
const hasLabelTooltip = this.hasSlotController.test('label-tooltip');
|
|
249
288
|
const hasLabel = this.label || hasLabelSlot;
|
|
@@ -309,7 +348,11 @@ export default class ZnCheckbox extends ZincElement implements ZincFormControl {
|
|
|
309
348
|
'checkbox--warning': effectiveColor === 'warning',
|
|
310
349
|
'checkbox--transparent': effectiveColor === 'transparent',
|
|
311
350
|
'checkbox--has-description': hasDescription,
|
|
312
|
-
'checkbox--has-selected-content': this.hasSlotController.test('selected-content')
|
|
351
|
+
'checkbox--has-selected-content': this.hasSlotController.test('selected-content'),
|
|
352
|
+
'selection-card': isCard,
|
|
353
|
+
'selection-card--has-image': hasImage,
|
|
354
|
+
[`selection-card--image-${this.imagePosition}`]: hasImage,
|
|
355
|
+
[`selection-card--control-${this.controlPosition}`]: isCard && this.controlPosition !== 'start'
|
|
313
356
|
})}>
|
|
314
357
|
|
|
315
358
|
<input
|
|
@@ -323,7 +366,7 @@ export default class ZnCheckbox extends ZincElement implements ZincFormControl {
|
|
|
323
366
|
.disabled=${this.disabled}
|
|
324
367
|
.required=${this.required}
|
|
325
368
|
aria-checked=${this.checked ? 'true' : 'false'}
|
|
326
|
-
aria-describedby=${hasDescription ? '' : '
|
|
369
|
+
aria-describedby=${hasDescription ? 'description' : ''}
|
|
327
370
|
@click=${this.handleClick}
|
|
328
371
|
@input=${this.handleInput}
|
|
329
372
|
@invalid=${this.handleInvalid}
|
|
@@ -334,7 +377,7 @@ export default class ZnCheckbox extends ZincElement implements ZincFormControl {
|
|
|
334
377
|
part="control${this.checked ? ' control--checked' : ''}${this.indeterminate
|
|
335
378
|
? ' control--indeterminate'
|
|
336
379
|
: ''}"
|
|
337
|
-
class="checkbox__control">
|
|
380
|
+
class="checkbox__control selection-card__control">
|
|
338
381
|
|
|
339
382
|
${this.checked && !this.indeterminate
|
|
340
383
|
? this.checkedIcon
|
|
@@ -367,8 +410,21 @@ export default class ZnCheckbox extends ZincElement implements ZincFormControl {
|
|
|
367
410
|
: ''}
|
|
368
411
|
</span>
|
|
369
412
|
|
|
370
|
-
|
|
371
|
-
|
|
413
|
+
${hasImage
|
|
414
|
+
? html`
|
|
415
|
+
<span part="image-container" class="selection-card__image-container">
|
|
416
|
+
<slot name="image">
|
|
417
|
+
${this.src
|
|
418
|
+
? html`<img part="image" class="selection-card__image" src=${this.src} alt=${this.imageAlt}>`
|
|
419
|
+
: ''}
|
|
420
|
+
</slot>
|
|
421
|
+
</span>`
|
|
422
|
+
: ''}
|
|
423
|
+
|
|
424
|
+
<div part="label" class="checkbox__label selection-card__content">
|
|
425
|
+
<span part="card-title" class="selection-card__title">
|
|
426
|
+
<slot></slot>${hasDefaultSlot ? '' : this.cardTitle}
|
|
427
|
+
</span>
|
|
372
428
|
<div
|
|
373
429
|
aria-hidden=${hasDescription ? 'false' : 'true'}
|
|
374
430
|
class="checkbox__description"
|
|
@@ -7,4 +7,60 @@ describe('<zn-checkbox>', () => {
|
|
|
7
7
|
|
|
8
8
|
expect(el).to.exist;
|
|
9
9
|
});
|
|
10
|
+
|
|
11
|
+
it('renders card content and a built-in image', async () => {
|
|
12
|
+
const el = await fixture(html`
|
|
13
|
+
<zn-checkbox
|
|
14
|
+
contained
|
|
15
|
+
card-title="Payroll"
|
|
16
|
+
description="Salary and pay schedule tools."
|
|
17
|
+
src="payroll.svg"
|
|
18
|
+
image-alt="Payroll illustration">
|
|
19
|
+
</zn-checkbox>
|
|
20
|
+
`);
|
|
21
|
+
|
|
22
|
+
const image = el.shadowRoot!.querySelector('[part="image"]')!;
|
|
23
|
+
expect(image.getAttribute('src')).to.equal('payroll.svg');
|
|
24
|
+
expect(image.getAttribute('alt')).to.equal('Payroll illustration');
|
|
25
|
+
expect(el.shadowRoot!.querySelector('[part="label"]')!.textContent).to.include('Payroll');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('renders card-title when formatting whitespace is present in the default slot', async () => {
|
|
29
|
+
const el = await fixture(html`
|
|
30
|
+
<zn-checkbox contained card-title="Benefits">
|
|
31
|
+
<zn-icon slot="image" src="featured_seasonal_and_gifts"></zn-icon>
|
|
32
|
+
</zn-checkbox>
|
|
33
|
+
`);
|
|
34
|
+
|
|
35
|
+
expect(el.shadowRoot!.querySelector('[part="card-title"]')!.textContent).to.include('Benefits');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('positions card images and controls', async () => {
|
|
39
|
+
const el = await fixture(html`
|
|
40
|
+
<zn-checkbox contained image-position="right" control-position="top-left">
|
|
41
|
+
Benefits
|
|
42
|
+
<zn-icon slot="image" src="featured_seasonal_and_gifts"></zn-icon>
|
|
43
|
+
</zn-checkbox>
|
|
44
|
+
`);
|
|
45
|
+
const base = el.shadowRoot!.querySelector('[part="base"]')!;
|
|
46
|
+
|
|
47
|
+
expect(base).to.have.class('selection-card--image-right');
|
|
48
|
+
expect(base).to.have.class('selection-card--control-top-left');
|
|
49
|
+
expect(el.shadowRoot!.querySelector('slot[name="image"]')).to.exist;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('leaves a plain contained checkbox without card styles', async () => {
|
|
53
|
+
const el = await fixture(html`<zn-checkbox contained description="Supporting text">Payroll</zn-checkbox>`);
|
|
54
|
+
|
|
55
|
+
expect(el.shadowRoot!.querySelector('[part="base"]')).not.to.have.class('selection-card');
|
|
56
|
+
expect(el.shadowRoot!.querySelector('[part="image-container"]')).to.be.null;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('can hide the visual control without removing the native input', async () => {
|
|
60
|
+
const el = await fixture(html`<zn-checkbox contained control-position="none">Payroll</zn-checkbox>`);
|
|
61
|
+
|
|
62
|
+
expect(el.shadowRoot!.querySelector('input[type="checkbox"]')).to.exist;
|
|
63
|
+
expect(el.shadowRoot!.querySelector('[part~="control"]')).to.exist;
|
|
64
|
+
expect(el.shadowRoot!.querySelector('[part="base"]')).to.have.class('selection-card--control-none');
|
|
65
|
+
});
|
|
10
66
|
});
|
|
@@ -34,6 +34,8 @@ import styles from './checkbox-group.scss';
|
|
|
34
34
|
* @csspart form-control-label - The label's wrapper.
|
|
35
35
|
* @csspart form-control-input - The input's wrapper.
|
|
36
36
|
* @csspart form-control-help-text - The help text's wrapper.
|
|
37
|
+
*
|
|
38
|
+
* @cssproperty --zn-checkbox-group-column-width - Minimum column width used by the horizontal contained grid, or the card basis when `wrap` is set.
|
|
37
39
|
*/
|
|
38
40
|
export default class ZnCheckboxGroup extends ZincElement implements ZincFormControl {
|
|
39
41
|
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
@@ -75,6 +77,15 @@ export default class ZnCheckboxGroup extends ZincElement implements ZincFormCont
|
|
|
75
77
|
/** The checkbox group's style. Changes the group's style from the default (plain) style to the 'contained' style. This style will be applied to all child checkboxes. */
|
|
76
78
|
@property({type: Boolean, reflect: true}) contained = false;
|
|
77
79
|
|
|
80
|
+
/** Squares off the corners of every contained checkbox in the group, which are rounded by default. */
|
|
81
|
+
@property({type: Boolean, reflect: true}) square = false;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Lays horizontal contained checkboxes out as a wrapping row instead of an equal-width grid, so each one sizes from
|
|
85
|
+
* `--zn-checkbox-group-column-width` (set it to `auto` to size from content) and wraps onto a new line when needed.
|
|
86
|
+
*/
|
|
87
|
+
@property({type: Boolean, reflect: true}) wrap = false;
|
|
88
|
+
|
|
78
89
|
/**
|
|
79
90
|
* By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you
|
|
80
91
|
* to place the form control outside of a form and associate it with the form that has this `id`. The form must be in
|
|
@@ -158,6 +169,10 @@ export default class ZnCheckboxGroup extends ZincElement implements ZincFormCont
|
|
|
158
169
|
checkbox.size = this.size;
|
|
159
170
|
checkbox.horizontal = this.horizontal;
|
|
160
171
|
|
|
172
|
+
if (this.square) {
|
|
173
|
+
checkbox.square = true;
|
|
174
|
+
}
|
|
175
|
+
|
|
161
176
|
// Add class to checkboxes in a Checkbox Group so that we can style them without using :slotted
|
|
162
177
|
checkbox.classList.add('groupedCheckbox');
|
|
163
178
|
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
:host([horizontal]) .form-control-input {
|
|
10
10
|
display: flex;
|
|
11
|
-
column-gap: var(--zn-spacing-2x-large);
|
|
12
11
|
flex-wrap: wrap;
|
|
12
|
+
column-gap: var(--zn-spacing-2x-large);
|
|
13
|
+
row-gap: var(--zn-spacing-small);
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
::slotted(zn-checkbox[horizontal]) {
|
|
@@ -17,15 +18,16 @@
|
|
|
17
18
|
min-width: 100px;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
/* The label supplies the gap above the options, so a group without one starts flush. */
|
|
22
|
+
:host([contained]) .form-control--has-label .form-control-input {
|
|
21
23
|
margin-top: var(--zn-spacing-medium);
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
:host([horizontal][contained]) .form-control-input {
|
|
25
|
-
margin-top: var(--zn-spacing-medium);
|
|
26
27
|
display: grid;
|
|
27
|
-
grid-template-columns: repeat(auto-fit, minmax(152px, 1fr));
|
|
28
|
-
|
|
28
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--zn-checkbox-group-column-width, 152px)), 1fr));
|
|
29
|
+
align-items: stretch;
|
|
30
|
+
gap: var(--zn-spacing-small);
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
::slotted(zn-checkbox) {
|
|
@@ -42,6 +44,22 @@
|
|
|
42
44
|
height: 100%;
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
/* `wrap` swaps the equal-width grid for a flex row: cards start from their basis and wrap onto new lines. */
|
|
48
|
+
:host([horizontal][contained][wrap]) .form-control-input {
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-wrap: wrap;
|
|
51
|
+
align-items: stretch;
|
|
52
|
+
gap: var(--zn-spacing-x-small);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
:host([wrap]) ::slotted(zn-checkbox[horizontal][contained]) {
|
|
56
|
+
flex: 1 1 var(--zn-checkbox-group-column-width, 152px);
|
|
57
|
+
min-width: 0;
|
|
58
|
+
|
|
59
|
+
/* A percentage height would resolve against every line, not the card's own. */
|
|
60
|
+
height: auto;
|
|
61
|
+
}
|
|
62
|
+
|
|
45
63
|
|
|
46
64
|
.form-control {
|
|
47
65
|
position: relative;
|
|
@@ -7,4 +7,19 @@ describe('<zn-checkbox-group>', () => {
|
|
|
7
7
|
|
|
8
8
|
expect(el).to.exist;
|
|
9
9
|
});
|
|
10
|
+
|
|
11
|
+
it('propagates contained and square to its checkboxs', async () => {
|
|
12
|
+
const el = await fixture<HTMLElement>(html`
|
|
13
|
+
<zn-checkbox-group contained square>
|
|
14
|
+
<zn-checkbox value="one">One</zn-checkbox>
|
|
15
|
+
<zn-checkbox value="two">Two</zn-checkbox>
|
|
16
|
+
</zn-checkbox-group>
|
|
17
|
+
`);
|
|
18
|
+
await new Promise(resolve => requestAnimationFrame(resolve));
|
|
19
|
+
|
|
20
|
+
el.querySelectorAll('zn-checkbox').forEach(child => {
|
|
21
|
+
expect(child).to.have.attribute('contained');
|
|
22
|
+
expect(child).to.have.attribute('square');
|
|
23
|
+
});
|
|
24
|
+
});
|
|
10
25
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {classMap} from "lit/directives/class-map.js";
|
|
2
2
|
import {type CSSResultGroup, html, nothing, type PropertyValues, unsafeCSS} from 'lit';
|
|
3
|
-
import {deepQuerySelectorAll} from "../../utilities/query";
|
|
3
|
+
import {deepQuerySelectorAll, idSelector} from "../../utilities/query";
|
|
4
4
|
import {HasSlotController} from "../../internal/slot";
|
|
5
5
|
import {ifDefined} from "lit/directives/if-defined.js";
|
|
6
6
|
import {property, query, state} from 'lit/decorators.js';
|
|
@@ -97,7 +97,7 @@ export default class ZnConfirm extends ZincElement {
|
|
|
97
97
|
const trigger = this.hasSlotController.getSlot('trigger');
|
|
98
98
|
trigger?.addEventListener('click', this.show);
|
|
99
99
|
} else if (this.trigger) {
|
|
100
|
-
const triggers = deepQuerySelectorAll(
|
|
100
|
+
const triggers = deepQuerySelectorAll(idSelector(this.trigger), this.parentElement as Element, '');
|
|
101
101
|
triggers.forEach((el: HTMLButtonElement) => {
|
|
102
102
|
el.addEventListener('click', this.show);
|
|
103
103
|
});
|
|
@@ -105,7 +105,7 @@ export default class ZnConfirm extends ZincElement {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
updateTriggers() {
|
|
108
|
-
const triggers = deepQuerySelectorAll(
|
|
108
|
+
const triggers = deepQuerySelectorAll(idSelector(this.trigger), this.parentElement as Element, '');
|
|
109
109
|
triggers.forEach((el: HTMLButtonElement) => {
|
|
110
110
|
// if already has a click listener, remove it
|
|
111
111
|
el.removeEventListener('click', this.show);
|
|
@@ -7,4 +7,19 @@ describe('<zn-confirm-modal>', () => {
|
|
|
7
7
|
|
|
8
8
|
expect(el).to.exist;
|
|
9
9
|
});
|
|
10
|
+
|
|
11
|
+
it('connects a trigger whose id contains CSS syntax characters', async () => {
|
|
12
|
+
const id = 'delete-enum-cloud_hosting_&_infrastructure';
|
|
13
|
+
const el = await fixture<HTMLElement>(html`
|
|
14
|
+
<div>
|
|
15
|
+
<button id="${id}">Delete</button>
|
|
16
|
+
<zn-confirm trigger="${id}" caption="Delete"></zn-confirm>
|
|
17
|
+
</div>
|
|
18
|
+
`);
|
|
19
|
+
const confirm = el.querySelector<HTMLElement & {updateComplete: Promise<void>}>('zn-confirm')!;
|
|
20
|
+
await confirm.updateComplete;
|
|
21
|
+
|
|
22
|
+
// An unescaped `#id` selector throws a SyntaxError here, leaving the component unrendered.
|
|
23
|
+
expect(confirm.shadowRoot!.querySelector('zn-dialog')).to.exist;
|
|
24
|
+
});
|
|
10
25
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {classMap} from "lit/directives/class-map.js";
|
|
2
2
|
import {type CSSResultGroup, html, type PropertyValues, unsafeCSS} from 'lit';
|
|
3
3
|
import {HasSlotController} from "../../internal/slot";
|
|
4
|
+
import {idSelector} from "../../utilities/query";
|
|
4
5
|
import {ifDefined} from "lit/directives/if-defined.js";
|
|
5
6
|
import {property, query} from 'lit/decorators.js';
|
|
6
7
|
import {unlockBodyScrolling} from "../../internal/scroll";
|
|
@@ -105,7 +106,7 @@ export default class ZnDialog extends ZincElement {
|
|
|
105
106
|
this.shadowRoot?.addEventListener('click', this.closeClickHandler);
|
|
106
107
|
|
|
107
108
|
if (this.trigger) {
|
|
108
|
-
const trigger = this.parentNode?.querySelector(
|
|
109
|
+
const trigger = this.parentNode?.querySelector(idSelector(this.trigger));
|
|
109
110
|
if (trigger) {
|
|
110
111
|
trigger.addEventListener('click', () => this.show());
|
|
111
112
|
}
|
|
@@ -132,7 +133,7 @@ export default class ZnDialog extends ZincElement {
|
|
|
132
133
|
this.removeEventListener('click', this.closeClickHandler);
|
|
133
134
|
|
|
134
135
|
if (this.trigger) {
|
|
135
|
-
const trigger = this.parentElement?.querySelector(
|
|
136
|
+
const trigger = this.parentElement?.querySelector(idSelector(this.trigger));
|
|
136
137
|
if (trigger) {
|
|
137
138
|
trigger.removeEventListener('click', () => this.show());
|
|
138
139
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {type CSSResultGroup, html, type PropertyValues, unsafeCSS} from 'lit';
|
|
2
|
-
import {deepQuerySelectorAll} from "../../utilities/query";
|
|
2
|
+
import {deepQuerySelectorAll, idSelector} from "../../utilities/query";
|
|
3
3
|
import {FormControlController} from '../../internal/form';
|
|
4
4
|
import {on} from "../../utilities/on";
|
|
5
5
|
import {property, query} from 'lit/decorators.js';
|
|
@@ -489,12 +489,13 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
|
|
|
489
489
|
|
|
490
490
|
// Find which element contains the content to insert
|
|
491
491
|
// Priority: currentTarget > selectedTarget > target > composedPath > querySelector
|
|
492
|
+
const contentSelector = idSelector(contentContainer);
|
|
492
493
|
const contentElement: Element | null =
|
|
493
|
-
(e.currentTarget instanceof Element && e.currentTarget.closest(
|
|
494
|
-
(e.selectedTarget instanceof Element && e.selectedTarget.closest(
|
|
495
|
-
(e.target instanceof Element && e.target.closest(
|
|
496
|
-
((e.composedPath()[0] as Element)?.closest(
|
|
497
|
-
deepQuerySelectorAll(
|
|
494
|
+
(e.currentTarget instanceof Element && e.currentTarget.closest(contentSelector)) ||
|
|
495
|
+
(e.selectedTarget instanceof Element && e.selectedTarget.closest(contentSelector)) ||
|
|
496
|
+
(e.target instanceof Element && e.target.closest(contentSelector)) ||
|
|
497
|
+
((e.composedPath()[0] as Element)?.closest(contentSelector)) ||
|
|
498
|
+
deepQuerySelectorAll(contentSelector, document.documentElement, '')[0];
|
|
498
499
|
if (!contentElement) return;
|
|
499
500
|
|
|
500
501
|
let content = contentElement.textContent;
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
&__caption {
|
|
18
18
|
font-weight: var(--zn-text-h1-font-weight);
|
|
19
19
|
font-size: var(--zn-text-h1-font-size);
|
|
20
|
+
text-transform: var(--zn-text-h1-transform, none);
|
|
21
|
+
letter-spacing: var(--zn-text-h1-letter-spacing, normal);
|
|
20
22
|
color: rgba(var(--zn-text-panel-title), 100%);
|
|
21
23
|
line-height: var(--zn-icon-size, 20px);
|
|
22
24
|
margin: 0;
|
|
@@ -110,6 +112,7 @@
|
|
|
110
112
|
width: 100%;
|
|
111
113
|
max-width: 100%;
|
|
112
114
|
height: fit-content;
|
|
115
|
+
min-height: var(--zn-input-height-medium, 36px);
|
|
113
116
|
gap: 10px;
|
|
114
117
|
margin: 0 auto;
|
|
115
118
|
padding: var(--zn-base-gap);
|
|
@@ -226,7 +229,6 @@
|
|
|
226
229
|
slot[name="actions"] {
|
|
227
230
|
justify-content: end;
|
|
228
231
|
flex-wrap: nowrap;
|
|
229
|
-
height: var(--zn-icon-size, 20px);
|
|
230
232
|
}
|
|
231
233
|
|
|
232
234
|
slot[name="actions"]::slotted(zn-button-menu) {
|
|
@@ -276,33 +276,36 @@ zn-popup::part(popup) {
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
|
|
279
|
+
// Colour variants fill the row on hover, so the label switches to the matching
|
|
280
|
+
// --zn-color-on-* ink rather than a hardcoded white (which disappears on a
|
|
281
|
+
// light accent such as velocity's neon lime).
|
|
279
282
|
.menu-item--primary {
|
|
280
283
|
--color: rgba(var(--zn-primary), 0.9);
|
|
281
|
-
--hover-color:
|
|
284
|
+
--hover-color: rgb(var(--zn-color-on-primary, 255, 255, 255));
|
|
282
285
|
--hover-bg: rgba(var(--zn-primary), 0.9);
|
|
283
286
|
}
|
|
284
287
|
|
|
285
288
|
.menu-item--error {
|
|
286
289
|
--color: rgb(var(--zn-color-error));
|
|
287
|
-
--hover-color:
|
|
290
|
+
--hover-color: rgb(var(--zn-color-on-error, 255, 255, 255));
|
|
288
291
|
--hover-bg: rgba(var(--zn-color-error), 0.9);
|
|
289
292
|
}
|
|
290
293
|
|
|
291
294
|
.menu-item--info {
|
|
292
295
|
--color: rgb(var(--zn-color-info));
|
|
293
|
-
--hover-color:
|
|
296
|
+
--hover-color: rgb(var(--zn-color-on-info, 255, 255, 255));
|
|
294
297
|
--hover-bg: rgba(var(--zn-color-info), 0.9);
|
|
295
298
|
}
|
|
296
299
|
|
|
297
300
|
.menu-item--warning {
|
|
298
301
|
--color: rgb(var(--zn-color-warning));
|
|
299
|
-
--hover-color:
|
|
302
|
+
--hover-color: rgb(var(--zn-color-on-warning, 255, 255, 255));
|
|
300
303
|
--hover-bg: rgba(var(--zn-color-warning), 0.9);
|
|
301
304
|
}
|
|
302
305
|
|
|
303
306
|
.menu-item--success {
|
|
304
307
|
--color: rgb(var(--zn-color-success));
|
|
305
|
-
--hover-color:
|
|
308
|
+
--hover-color: rgb(var(--zn-color-on-success, 255, 255, 255));
|
|
306
309
|
--hover-bg: rgba(var(--zn-color-success), 0.9);
|
|
307
310
|
}
|
|
308
311
|
|
|
@@ -303,6 +303,17 @@ ul.navbar.navbar--stacked.navbar--icon-bar {
|
|
|
303
303
|
}
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
+
// Icon-bar items are standalone pills, not tabs — the tab-strip rule above
|
|
307
|
+
// leaves them with a top+right border only, which reads as an unfinished box
|
|
308
|
+
:host([icon-bar]:not([stacked])) {
|
|
309
|
+
ul.navbar {
|
|
310
|
+
> li {
|
|
311
|
+
border-width: 1px;
|
|
312
|
+
border-radius: 6px;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
306
317
|
:host {
|
|
307
318
|
display: block;
|
|
308
319
|
width: 100%;
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
height: 100%;
|
|
9
9
|
min-height: 0;
|
|
10
10
|
container-type: inline-size;
|
|
11
|
+
// Optional decorative layer tiled across the page canvas (header + content).
|
|
12
|
+
// A theme sets it to an image and its tile size; unset it costs nothing.
|
|
13
|
+
--zn-page-texture: none;
|
|
14
|
+
--zn-page-texture-size: auto;
|
|
11
15
|
--zn-page-content-background: var(--zn-body);
|
|
12
16
|
--zn-page-header-background: var(--zn-page-surface-background, var(--zn-page-background, var(--zn-body)));
|
|
13
17
|
}
|
|
@@ -30,7 +34,11 @@
|
|
|
30
34
|
top: 0;
|
|
31
35
|
z-index: 2;
|
|
32
36
|
box-sizing: border-box;
|
|
33
|
-
background: rgba(var(--zn-page-header-background), 95%);
|
|
37
|
+
background-color: rgba(var(--zn-page-header-background), 95%);
|
|
38
|
+
background-image: var(--zn-page-texture);
|
|
39
|
+
background-repeat: repeat;
|
|
40
|
+
background-position: center top;
|
|
41
|
+
background-size: var(--zn-page-texture-size);
|
|
34
42
|
|
|
35
43
|
&::after {
|
|
36
44
|
content: '';
|
|
@@ -173,6 +181,8 @@
|
|
|
173
181
|
&__caption {
|
|
174
182
|
font-weight: var(--zn-text-h1-font-weight);
|
|
175
183
|
font-size: var(--zn-text-h1-font-size);
|
|
184
|
+
text-transform: var(--zn-text-h1-transform, none);
|
|
185
|
+
letter-spacing: var(--zn-text-h1-letter-spacing, normal);
|
|
176
186
|
color: rgba(var(--zn-text-panel-title), 100%);
|
|
177
187
|
line-height: var(--zn-spacing-medium);
|
|
178
188
|
margin: 0;
|
|
@@ -303,7 +313,11 @@
|
|
|
303
313
|
overflow: visible;
|
|
304
314
|
box-sizing: border-box;
|
|
305
315
|
padding: 0 var(--zn-base-gap) var(--zn-base-gap);
|
|
306
|
-
background: rgb(var(--zn-page-content-background, var(--zn-body)));
|
|
316
|
+
background-color: rgb(var(--zn-page-content-background, var(--zn-body)));
|
|
317
|
+
background-image: var(--zn-page-texture);
|
|
318
|
+
background-repeat: repeat;
|
|
319
|
+
background-position: center top;
|
|
320
|
+
background-size: var(--zn-page-texture-size);
|
|
307
321
|
}
|
|
308
322
|
|
|
309
323
|
.page__tabs .page__content {
|