@kubex/zinc 1.1.69 → 1.1.70
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/custom-elements.json +307 -12
- package/dist/vscode.html-custom-data.json +13 -4
- package/dist/web-types.json +49 -11
- package/dist/zn.d.ts +75 -7
- package/dist/zn.min.js +275 -224
- package/docs/pages/components/icon-picker.md +14 -0
- package/docs/pages/components/input.md +19 -1
- package/docs/pages/components/theme-editor.md +52 -18
- package/package.json +1 -1
- package/src/components/collapsible/collapsible.scss +0 -10
- package/src/components/file/file.component.ts +15 -0
- package/src/components/icon-picker/icon-picker.component.ts +150 -16
- package/src/components/icon-picker/icon-picker.scss +32 -0
- package/src/components/input/input.component.ts +44 -21
- package/src/components/input/input.scss +134 -42
- package/src/components/input/input.test.ts +45 -0
- package/src/components/theme-editor/theme-editor.component.ts +166 -9
- package/src/components/theme-editor/theme-editor.test.ts +168 -0
|
@@ -970,6 +970,57 @@ describe('<zn-theme-editor>', () => {
|
|
|
970
970
|
expect(el.shadowRoot!.querySelectorAll('.editor__section').length).to.equal(0);
|
|
971
971
|
});
|
|
972
972
|
|
|
973
|
+
it("expands the first tab's first group on load, and closing it stays closed", async () => {
|
|
974
|
+
const el = await fixture(html`
|
|
975
|
+
<zn-theme-editor
|
|
976
|
+
src="about:blank" frame-origin="https://site.example"
|
|
977
|
+
.sections="${[
|
|
978
|
+
{name: 'colors', caption: 'Colors', groups: [
|
|
979
|
+
{name: 'brand', caption: 'Brand'},
|
|
980
|
+
{name: 'semantic', caption: 'Semantic'},
|
|
981
|
+
]},
|
|
982
|
+
]}">
|
|
983
|
+
<zn-input slot="brand" name="brand" value="1"></zn-input>
|
|
984
|
+
<zn-input slot="semantic" name="semantic" value="2"></zn-input>
|
|
985
|
+
</zn-theme-editor>`);
|
|
986
|
+
|
|
987
|
+
const sections = el.shadowRoot!.querySelectorAll<HTMLElement & {expanded: boolean}>('.editor__section');
|
|
988
|
+
expect([sections[0].expanded, sections[1].expanded]).to.deep.equal([true, false]);
|
|
989
|
+
|
|
990
|
+
// The auto-expand is one-shot - a later render must not reopen it.
|
|
991
|
+
sections[0].expanded = false;
|
|
992
|
+
(el as HTMLElement & {requestUpdate: () => void}).requestUpdate();
|
|
993
|
+
await (el as HTMLElement & {updateComplete: Promise<boolean>}).updateComplete;
|
|
994
|
+
expect(sections[0].expanded).to.be.false;
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
it("clicking a tab expands its first group, and leaves an already-open group alone", async () => {
|
|
998
|
+
const el = await NESTED_GROUPS_FIXTURE();
|
|
999
|
+
const sections = el.shadowRoot!.querySelectorAll<HTMLElement & {expanded: boolean}>('.editor__section');
|
|
1000
|
+
const panels = el.shadowRoot!.querySelectorAll('.editor__tab-panel');
|
|
1001
|
+
const tabs = el.shadowRoot!.querySelectorAll<HTMLElement>('li[tab]');
|
|
1002
|
+
await waitUntil(() => panels[0].hasAttribute('selected'));
|
|
1003
|
+
|
|
1004
|
+
// Colors: Brand (closed) + Semantic (open: true). Shapes: Radius (closed).
|
|
1005
|
+
expect([sections[0].expanded, sections[1].expanded, sections[2].expanded])
|
|
1006
|
+
.to.deep.equal([false, true, false]);
|
|
1007
|
+
|
|
1008
|
+
tabs[1].click();
|
|
1009
|
+
await waitUntil(() => panels[1].hasAttribute('selected'));
|
|
1010
|
+
expect(sections[2].expanded).to.be.true;
|
|
1011
|
+
|
|
1012
|
+
// Semantic is already open, so Brand stays closed.
|
|
1013
|
+
tabs[0].click();
|
|
1014
|
+
await waitUntil(() => panels[0].hasAttribute('selected'));
|
|
1015
|
+
expect(sections[0].expanded).to.be.false;
|
|
1016
|
+
expect(sections[1].expanded).to.be.true;
|
|
1017
|
+
|
|
1018
|
+
// With every group closed, the first one opens.
|
|
1019
|
+
sections[1].expanded = false;
|
|
1020
|
+
tabs[0].click();
|
|
1021
|
+
expect(sections[0].expanded).to.be.true;
|
|
1022
|
+
});
|
|
1023
|
+
|
|
973
1024
|
it('does not crash render when groups is malformed', async () => {
|
|
974
1025
|
const el = await fixture(html`
|
|
975
1026
|
<zn-theme-editor
|
|
@@ -983,6 +1034,123 @@ describe('<zn-theme-editor>', () => {
|
|
|
983
1034
|
});
|
|
984
1035
|
});
|
|
985
1036
|
|
|
1037
|
+
describe('structure derived from group/category attributes', () => {
|
|
1038
|
+
const DERIVED_FIXTURE = () => fixture(html`
|
|
1039
|
+
<zn-theme-editor src="about:blank" frame-origin="https://site.example" debounce="10">
|
|
1040
|
+
<zn-input group="Background & Foreground" category="Colors" name="bg" value="#ffffff"></zn-input>
|
|
1041
|
+
<zn-input group="Background & Foreground" category="Colors" name="fg" value="#000000"></zn-input>
|
|
1042
|
+
<zn-input group="Background & Foreground" category="Spacing" name="gap" value="8"></zn-input>
|
|
1043
|
+
<zn-input group="Typography" category="Colors" name="font" value="Inter"></zn-input>
|
|
1044
|
+
</zn-theme-editor>`);
|
|
1045
|
+
|
|
1046
|
+
it('renders and is accessible', async () => {
|
|
1047
|
+
const el = await DERIVED_FIXTURE();
|
|
1048
|
+
await expect(el).to.be.accessible();
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
it('creates a tab per group and a collapsible per category within it', async () => {
|
|
1052
|
+
const el = await DERIVED_FIXTURE();
|
|
1053
|
+
|
|
1054
|
+
const tabs = Array.from(el.shadowRoot!.querySelectorAll('li[tab]')).map(t => t.textContent?.trim());
|
|
1055
|
+
expect(tabs).to.deep.equal(['Background & Foreground', 'Typography']);
|
|
1056
|
+
|
|
1057
|
+
const captions = Array.from(el.shadowRoot!.querySelectorAll('.editor__section'))
|
|
1058
|
+
.map(s => s.getAttribute('caption'));
|
|
1059
|
+
expect(captions).to.deep.equal(['Colors', 'Spacing', 'Colors']);
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
it('slots each control into its derived collapsible and harvests them all', async () => {
|
|
1063
|
+
const el = await DERIVED_FIXTURE();
|
|
1064
|
+
|
|
1065
|
+
// Two tabs each hold a "Colors" category - their slots must not collide.
|
|
1066
|
+
const bgSlot = el.querySelector('zn-input[name="bg"]')!.getAttribute('slot');
|
|
1067
|
+
const fontSlot = el.querySelector('zn-input[name="font"]')!.getAttribute('slot');
|
|
1068
|
+
expect(bgSlot).to.be.a('string').and.not.equal('');
|
|
1069
|
+
expect(fontSlot).to.not.equal(bgSlot);
|
|
1070
|
+
// controls sharing a group+category land in the same slot
|
|
1071
|
+
expect(el.querySelector('zn-input[name="fg"]')!.getAttribute('slot')).to.equal(bgSlot);
|
|
1072
|
+
|
|
1073
|
+
expect((el as HTMLElement & {values: {light: Record<string, unknown>}}).values.light)
|
|
1074
|
+
.to.deep.equal({bg: '#ffffff', fg: '#000000', gap: '8', font: 'Inter'});
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
it('puts a control with only group directly in its tab', async () => {
|
|
1078
|
+
const el = await fixture(html`
|
|
1079
|
+
<zn-theme-editor src="about:blank" frame-origin="https://site.example">
|
|
1080
|
+
<zn-input group="Colors" category="Brand" name="accent" value="#6936f5"></zn-input>
|
|
1081
|
+
<zn-input group="Colors" name="loose" value="x"></zn-input>
|
|
1082
|
+
</zn-theme-editor>`);
|
|
1083
|
+
|
|
1084
|
+
expect(el.shadowRoot!.querySelectorAll('li[tab]').length).to.equal(1);
|
|
1085
|
+
const captions = Array.from(el.shadowRoot!.querySelectorAll('.editor__section'))
|
|
1086
|
+
.map(s => s.getAttribute('caption'));
|
|
1087
|
+
expect(captions).to.deep.equal(['Brand']);
|
|
1088
|
+
expect((el as HTMLElement & {values: {light: Record<string, unknown>}}).values.light)
|
|
1089
|
+
.to.deep.equal({accent: '#6936f5', loose: 'x'});
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
it('treats a control with only category as its own top-level section', async () => {
|
|
1093
|
+
const el = await fixture(html`
|
|
1094
|
+
<zn-theme-editor src="about:blank" frame-origin="https://site.example">
|
|
1095
|
+
<zn-input category="Colors" name="accent" value="#6936f5"></zn-input>
|
|
1096
|
+
</zn-theme-editor>`);
|
|
1097
|
+
|
|
1098
|
+
const captions = Array.from(el.shadowRoot!.querySelectorAll('.editor__section'))
|
|
1099
|
+
.map(s => s.getAttribute('caption'));
|
|
1100
|
+
expect(captions).to.deep.equal(['Colors']);
|
|
1101
|
+
expect((el as HTMLElement & {values: {light: Record<string, unknown>}}).values.light)
|
|
1102
|
+
.to.deep.equal({accent: '#6936f5'});
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
it('an explicit sections wins over the attributes', async () => {
|
|
1106
|
+
const el = await fixture(html`
|
|
1107
|
+
<zn-theme-editor
|
|
1108
|
+
src="about:blank" frame-origin="https://site.example"
|
|
1109
|
+
.sections="${[{name: 'explicit', caption: 'Explicit'}]}">
|
|
1110
|
+
<zn-input slot="explicit" group="Ignored" category="Ignored too" name="accent" value="#6936f5"></zn-input>
|
|
1111
|
+
</zn-theme-editor>`);
|
|
1112
|
+
|
|
1113
|
+
const captions = Array.from(el.shadowRoot!.querySelectorAll('.editor__section'))
|
|
1114
|
+
.map(s => s.getAttribute('caption'));
|
|
1115
|
+
expect(captions).to.deep.equal(['Explicit']);
|
|
1116
|
+
// the explicit slot assignment is left untouched
|
|
1117
|
+
expect(el.querySelector('zn-input')!.getAttribute('slot')).to.equal('explicit');
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
it('derives, slots and pushes a control added after mount', async () => {
|
|
1121
|
+
const el = await fixture(html`
|
|
1122
|
+
<zn-theme-editor src="about:blank" frame-origin="https://site.example">
|
|
1123
|
+
<zn-input group="Colors" category="Brand" name="accent" value="#6936f5"></zn-input>
|
|
1124
|
+
</zn-theme-editor>`);
|
|
1125
|
+
const calls = spyOnFrame(el);
|
|
1126
|
+
expect(el.shadowRoot!.querySelectorAll('li[tab]').length).to.equal(1);
|
|
1127
|
+
|
|
1128
|
+
const added = document.createElement('zn-input');
|
|
1129
|
+
added.setAttribute('group', 'Shapes');
|
|
1130
|
+
added.setAttribute('category', 'Radius');
|
|
1131
|
+
added.setAttribute('name', 'radius');
|
|
1132
|
+
added.setAttribute('value', '4');
|
|
1133
|
+
el.append(added);
|
|
1134
|
+
|
|
1135
|
+
await waitUntil(() => calls.length >= 1);
|
|
1136
|
+
expect((calls[0]['values'] as Record<string, unknown>)['radius']).to.equal('4');
|
|
1137
|
+
await waitUntil(() => el.shadowRoot!.querySelectorAll('li[tab]').length === 2);
|
|
1138
|
+
expect(added.getAttribute('slot')).to.be.a('string').and.not.equal('');
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
it('leaves controls with neither attribute in the default slot', async () => {
|
|
1142
|
+
const el = await fixture(html`
|
|
1143
|
+
<zn-theme-editor src="about:blank" frame-origin="https://site.example">
|
|
1144
|
+
<zn-input group="Colors" category="Brand" name="accent" value="#6936f5"></zn-input>
|
|
1145
|
+
<zn-input name="loose" value="x"></zn-input>
|
|
1146
|
+
</zn-theme-editor>`);
|
|
1147
|
+
|
|
1148
|
+
expect(el.querySelector('zn-input[name="loose"]')!.hasAttribute('slot')).to.be.false;
|
|
1149
|
+
expect((el as HTMLElement & {values: {light: Record<string, unknown>}}).values.light)
|
|
1150
|
+
.to.deep.equal({accent: '#6936f5', loose: 'x'});
|
|
1151
|
+
});
|
|
1152
|
+
});
|
|
1153
|
+
|
|
986
1154
|
describe('preview sources', () => {
|
|
987
1155
|
it('with sources unset, renders no dropdown and leaves src alone', async () => {
|
|
988
1156
|
const el = await fixture(html`
|