@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.
@@ -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 &amp; Foreground" category="Colors" name="bg" value="#ffffff"></zn-input>
1041
+ <zn-input group="Background &amp; Foreground" category="Colors" name="fg" value="#000000"></zn-input>
1042
+ <zn-input group="Background &amp; 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`