@ctrliq/quantic-components 1.85.0 → 1.85.1
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/dist/alert/alert.js +2 -1
- package/dist/alert/index.stories.d.ts +7 -0
- package/dist/alert/index.stories.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/meta/index.js.map +1 -1
- package/dist/meta.json +1 -1
- package/dist/quantic-components.js +13 -4
- package/dist/quantic-components.js.map +1 -1
- package/dist/quantic-components.min.js +1846 -1845
- package/dist/quantic-components.min.js.map +1 -1
- package/dist/register.js.map +1 -1
- package/dist/shared/auto-hide-empty-slots.mixin.d.ts +0 -35
- package/dist/shared/auto-hide-empty-slots.mixin.js +11 -3
- package/dist/shared/auto-hide-empty-slots.mixin.test.d.ts +1 -0
- package/dist/shared/auto-hide-empty-slots.mixin.test.js +57 -0
- package/dist/types/alert/index.stories.d.ts +7 -0
- package/dist/types/shared/auto-hide-empty-slots.mixin.d.ts +0 -35
- package/dist/types/shared/auto-hide-empty-slots.mixin.test.d.ts +1 -0
- package/package.json +1 -1
|
@@ -1,38 +1,3 @@
|
|
|
1
1
|
import { LitConstructor } from './shared.types';
|
|
2
2
|
import { QuanticElement } from './quantic-element';
|
|
3
|
-
/**
|
|
4
|
-
* Mixin that automatically hides empty slots.
|
|
5
|
-
*
|
|
6
|
-
* Each listed slot is hidden by default and revealed when the host has matching
|
|
7
|
-
* light DOM content. Pass `''` for the default (unnamed) slot.
|
|
8
|
-
*
|
|
9
|
-
* Two CSS rules cover all browsers for named slots:
|
|
10
|
-
*
|
|
11
|
-
* - `:host(:has([slot="name"]))` — Safari and Firefox
|
|
12
|
-
* - `@scope { :scope:has([slot="name"]) }` — Chrome (@scope changes the
|
|
13
|
-
* evaluation context so :has() can see light DOM children)
|
|
14
|
-
*
|
|
15
|
-
* The default slot uses `:has(> :not([slot]))` (element children without a
|
|
16
|
-
* `slot` attribute). Text-only default content is covered by the slotchange
|
|
17
|
-
* JS path below.
|
|
18
|
-
*
|
|
19
|
-
* The reveal value is `display: revert-layer` rather than `display: block` so
|
|
20
|
-
* that the component's own display value is restored instead of overridden.
|
|
21
|
-
* Component styles are placed in `@layer quantic-component` and the
|
|
22
|
-
* hide/reveal rules in `@layer quantic-slot-visibility` (declared after, so
|
|
23
|
-
* higher priority). `revert-layer` then falls back to whatever the component
|
|
24
|
-
* set in `quantic-component`.
|
|
25
|
-
*
|
|
26
|
-
* CSS is injected via `finalizeStyles` so it lands in Lit SSR's Declarative
|
|
27
|
-
* Shadow DOM output — no JS, no layout shift for SSR.
|
|
28
|
-
*
|
|
29
|
-
* Chrome doesn't re-evaluate :has() in shadow DOM when light-DOM children
|
|
30
|
-
* arrive asynchronously (e.g. React CSR). A capture listener on the shadow
|
|
31
|
-
* root intercepts all slotchange events internally and sets inline styles to
|
|
32
|
-
* override the CSS — no wiring required in component templates.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* class MyElement extends AutoHideEmptySlotsMixin(QuanticElement, ['header', 'footer']) {}
|
|
36
|
-
* class Toast extends AutoHideEmptySlotsMixin(QuanticElement, ['title', '', 'action']) {}
|
|
37
|
-
*/
|
|
38
3
|
export declare const AutoHideEmptySlotsMixin: <T extends LitConstructor<QuanticElement>>(superClass: T, slots?: string[]) => T;
|
|
@@ -39,13 +39,20 @@ import { escapeCssString, whenSlotPresent } from './slot-utils';
|
|
|
39
39
|
*
|
|
40
40
|
* Chrome doesn't re-evaluate :has() in shadow DOM when light-DOM children
|
|
41
41
|
* arrive asynchronously (e.g. React CSR). A capture listener on the shadow
|
|
42
|
-
* root intercepts all slotchange events internally and
|
|
43
|
-
*
|
|
42
|
+
* root intercepts all slotchange events internally and marks filled slots with
|
|
43
|
+
* `data-slot-filled`, no wiring required in component templates.
|
|
44
|
+
*
|
|
45
|
+
* That reveal has to be a rule inside `quantic-slot-visibility`, not an inline
|
|
46
|
+
* style. Inline declarations sit in the implicit unlayered layer, which the
|
|
47
|
+
* cascade orders after every explicit layer, so an inline
|
|
48
|
+
* `display: revert-layer` reverts to the `display: none` above rather than
|
|
49
|
+
* past it.
|
|
44
50
|
*
|
|
45
51
|
* @example
|
|
46
52
|
* class MyElement extends AutoHideEmptySlotsMixin(QuanticElement, ['header', 'footer']) {}
|
|
47
53
|
* class Toast extends AutoHideEmptySlotsMixin(QuanticElement, ['title', '', 'action']) {}
|
|
48
54
|
*/
|
|
55
|
+
const FILLED_ATTRIBUTE = 'data-slot-filled';
|
|
49
56
|
export const AutoHideEmptySlotsMixin = (superClass, slots = []) => {
|
|
50
57
|
var _AutoHideEmptySlotsClass_instances, _AutoHideEmptySlotsClass_slotListenerAttached, _AutoHideEmptySlotsClass_isMeaningful, _AutoHideEmptySlotsClass_onSlotChange, _AutoHideEmptySlotsClass_attachSlotListener, _AutoHideEmptySlotsClass_detachSlotListener;
|
|
51
58
|
if (slots.length === 0)
|
|
@@ -63,6 +70,7 @@ export const AutoHideEmptySlotsMixin = (superClass, slots = []) => {
|
|
|
63
70
|
`${sel} { display: none; }`,
|
|
64
71
|
whenSlotPresent(name, unsafeCSS(`${sel} { display: revert-layer; }`))
|
|
65
72
|
.cssText,
|
|
73
|
+
`${sel}[${FILLED_ATTRIBUTE}] { display: revert-layer; }`,
|
|
66
74
|
].join(' ');
|
|
67
75
|
})
|
|
68
76
|
.join(' ');
|
|
@@ -84,7 +92,7 @@ export const AutoHideEmptySlotsMixin = (superClass, slots = []) => {
|
|
|
84
92
|
const hasContent = slot
|
|
85
93
|
.assignedNodes({ flatten: true })
|
|
86
94
|
.some(__classPrivateFieldGet(this, _AutoHideEmptySlotsClass_isMeaningful, "f"));
|
|
87
|
-
slot.
|
|
95
|
+
slot.toggleAttribute(FILLED_ATTRIBUTE, hasContent);
|
|
88
96
|
});
|
|
89
97
|
}
|
|
90
98
|
static finalizeStyles(styles) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { css } from 'lit';
|
|
5
|
+
import postcss from 'postcss';
|
|
6
|
+
import { describe, expect, it } from 'vitest';
|
|
7
|
+
import { AutoHideEmptySlotsMixin } from './auto-hide-empty-slots.mixin';
|
|
8
|
+
import { QuanticElement } from './quantic-element';
|
|
9
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const VISIBILITY_LAYER = 'quantic-slot-visibility';
|
|
11
|
+
class Probe extends AutoHideEmptySlotsMixin(QuanticElement, ['title', '']) {
|
|
12
|
+
}
|
|
13
|
+
Probe.styles = css `
|
|
14
|
+
slot[name='title'] {
|
|
15
|
+
display: flex;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
slot:not([name]) {
|
|
19
|
+
display: grid;
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
22
|
+
function visibilityLayer() {
|
|
23
|
+
const [sheet] = Probe.finalizeStyles(Probe.styles);
|
|
24
|
+
let found;
|
|
25
|
+
postcss.parse(sheet.cssText).walkAtRules('layer', (rule) => {
|
|
26
|
+
if (rule.params === VISIBILITY_LAYER && rule.nodes)
|
|
27
|
+
found = rule.nodes;
|
|
28
|
+
});
|
|
29
|
+
if (!found)
|
|
30
|
+
throw new Error(`no @layer ${VISIBILITY_LAYER} in the stylesheet`);
|
|
31
|
+
return found;
|
|
32
|
+
}
|
|
33
|
+
function displayFor(layer, selector) {
|
|
34
|
+
const rule = layer.find((node) => node.type === 'rule' && node.selector === selector);
|
|
35
|
+
return rule?.nodes
|
|
36
|
+
.find((node) => node.type === 'decl' && node.prop === 'display')
|
|
37
|
+
?.toString();
|
|
38
|
+
}
|
|
39
|
+
describe('AutoHideEmptySlotsMixin', () => {
|
|
40
|
+
const layer = visibilityLayer();
|
|
41
|
+
it.each([
|
|
42
|
+
['named', 'slot[name="title"]'],
|
|
43
|
+
['default', 'slot:not([name])'],
|
|
44
|
+
])('hides the %s slot by default', (_label, selector) => {
|
|
45
|
+
expect(displayFor(layer, selector)).toBe('display: none');
|
|
46
|
+
});
|
|
47
|
+
it.each([
|
|
48
|
+
['named', 'slot[name="title"][data-slot-filled]'],
|
|
49
|
+
['default', 'slot:not([name])[data-slot-filled]'],
|
|
50
|
+
])('reveals the %s slot from inside the visibility layer once JS marks it filled', (_label, selector) => {
|
|
51
|
+
expect(displayFor(layer, selector)).toBe('display: revert-layer');
|
|
52
|
+
});
|
|
53
|
+
it('never reveals a slot through an inline style', () => {
|
|
54
|
+
const source = readFileSync(join(HERE, 'auto-hide-empty-slots.mixin.ts'), 'utf8');
|
|
55
|
+
expect(source).not.toMatch(/\.style\.display\s*=/);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -22,6 +22,13 @@ export declare const CustomIcon: import("../shared/story").StoryObject<object>;
|
|
|
22
22
|
export declare const AsyncSlotRepro: {
|
|
23
23
|
name: string;
|
|
24
24
|
render: () => string;
|
|
25
|
+
parameters: {
|
|
26
|
+
docs: {
|
|
27
|
+
story: {
|
|
28
|
+
autoplay: boolean;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
};
|
|
25
32
|
play: ({ canvasElement }: {
|
|
26
33
|
canvasElement: HTMLElement;
|
|
27
34
|
}) => Promise<void>;
|
|
@@ -1,38 +1,3 @@
|
|
|
1
1
|
import { LitConstructor } from './shared.types';
|
|
2
2
|
import { QuanticElement } from './quantic-element';
|
|
3
|
-
/**
|
|
4
|
-
* Mixin that automatically hides empty slots.
|
|
5
|
-
*
|
|
6
|
-
* Each listed slot is hidden by default and revealed when the host has matching
|
|
7
|
-
* light DOM content. Pass `''` for the default (unnamed) slot.
|
|
8
|
-
*
|
|
9
|
-
* Two CSS rules cover all browsers for named slots:
|
|
10
|
-
*
|
|
11
|
-
* - `:host(:has([slot="name"]))` — Safari and Firefox
|
|
12
|
-
* - `@scope { :scope:has([slot="name"]) }` — Chrome (@scope changes the
|
|
13
|
-
* evaluation context so :has() can see light DOM children)
|
|
14
|
-
*
|
|
15
|
-
* The default slot uses `:has(> :not([slot]))` (element children without a
|
|
16
|
-
* `slot` attribute). Text-only default content is covered by the slotchange
|
|
17
|
-
* JS path below.
|
|
18
|
-
*
|
|
19
|
-
* The reveal value is `display: revert-layer` rather than `display: block` so
|
|
20
|
-
* that the component's own display value is restored instead of overridden.
|
|
21
|
-
* Component styles are placed in `@layer quantic-component` and the
|
|
22
|
-
* hide/reveal rules in `@layer quantic-slot-visibility` (declared after, so
|
|
23
|
-
* higher priority). `revert-layer` then falls back to whatever the component
|
|
24
|
-
* set in `quantic-component`.
|
|
25
|
-
*
|
|
26
|
-
* CSS is injected via `finalizeStyles` so it lands in Lit SSR's Declarative
|
|
27
|
-
* Shadow DOM output — no JS, no layout shift for SSR.
|
|
28
|
-
*
|
|
29
|
-
* Chrome doesn't re-evaluate :has() in shadow DOM when light-DOM children
|
|
30
|
-
* arrive asynchronously (e.g. React CSR). A capture listener on the shadow
|
|
31
|
-
* root intercepts all slotchange events internally and sets inline styles to
|
|
32
|
-
* override the CSS — no wiring required in component templates.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* class MyElement extends AutoHideEmptySlotsMixin(QuanticElement, ['header', 'footer']) {}
|
|
36
|
-
* class Toast extends AutoHideEmptySlotsMixin(QuanticElement, ['title', '', 'action']) {}
|
|
37
|
-
*/
|
|
38
3
|
export declare const AutoHideEmptySlotsMixin: <T extends LitConstructor<QuanticElement>>(superClass: T, slots?: string[]) => T;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|