@aquera/nile-elements 2.0.3 → 2.0.5

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 (88) hide show
  1. package/README.md +21 -0
  2. package/demo/nxtgen-classes.css +596 -2
  3. package/demo/nxtgen-dark.css +240 -21
  4. package/demo/nxtgen-properties.css +2 -2
  5. package/demo/nxtgen-utilities.css +598 -4
  6. package/demo/nxtgen.css +57 -2
  7. package/dist/index.cjs.js +1 -1
  8. package/dist/index.esm.js +1 -1
  9. package/dist/index.js +458 -317
  10. package/dist/nile-combobox/nile-combobox.cjs.js +1 -1
  11. package/dist/nile-combobox/nile-combobox.cjs.js.map +1 -1
  12. package/dist/nile-combobox/nile-combobox.css.cjs.js +1 -1
  13. package/dist/nile-combobox/nile-combobox.css.cjs.js.map +1 -1
  14. package/dist/nile-combobox/nile-combobox.css.esm.js +28 -15
  15. package/dist/nile-combobox/nile-combobox.esm.js +11 -8
  16. package/dist/nile-combobox/portal-manager.cjs.js +1 -1
  17. package/dist/nile-combobox/portal-manager.cjs.js.map +1 -1
  18. package/dist/nile-combobox/portal-manager.esm.js +1 -1
  19. package/dist/nile-combobox/renderer.cjs.js +1 -1
  20. package/dist/nile-combobox/renderer.cjs.js.map +1 -1
  21. package/dist/nile-combobox/renderer.esm.js +31 -43
  22. package/dist/nile-empty-state/nile-empty-state.cjs.js +1 -1
  23. package/dist/nile-empty-state/nile-empty-state.cjs.js.map +1 -1
  24. package/dist/nile-empty-state/nile-empty-state.css.cjs.js +1 -1
  25. package/dist/nile-empty-state/nile-empty-state.css.cjs.js.map +1 -1
  26. package/dist/nile-empty-state/nile-empty-state.css.esm.js +129 -6
  27. package/dist/nile-empty-state/nile-empty-state.esm.js +45 -31
  28. package/dist/nile-pagination/nile-pagination.cjs.js +1 -1
  29. package/dist/nile-pagination/nile-pagination.cjs.js.map +1 -1
  30. package/dist/nile-pagination/nile-pagination.esm.js +25 -25
  31. package/dist/nile-wysiwyg-editor/engine/create-state.cjs.js +1 -1
  32. package/dist/nile-wysiwyg-editor/engine/create-state.esm.js +1 -1
  33. package/dist/nile-wysiwyg-editor/engine/index.cjs.js +1 -1
  34. package/dist/nile-wysiwyg-editor/engine/index.esm.js +1 -1
  35. package/dist/nile-wysiwyg-editor/engine/plugins/code-highlight.cjs.js +1 -1
  36. package/dist/nile-wysiwyg-editor/engine/plugins/code-highlight.cjs.js.map +1 -1
  37. package/dist/nile-wysiwyg-editor/engine/plugins/code-highlight.esm.js +1 -1
  38. package/dist/nile-wysiwyg-editor/index.cjs.js +1 -1
  39. package/dist/nile-wysiwyg-editor/index.esm.js +1 -1
  40. package/dist/nile-wysiwyg-editor/nile-wysiwyg-editor.cjs.js +1 -1
  41. package/dist/nile-wysiwyg-editor/nile-wysiwyg-editor.esm.js +1 -1
  42. package/dist/nile-wysiwyg-editor/ui/code-block-menu.cjs.js +1 -1
  43. package/dist/nile-wysiwyg-editor/ui/code-block-menu.esm.js +1 -1
  44. package/dist/nile-wysiwyg-editor/ui/source-view.cjs.js +1 -1
  45. package/dist/nile-wysiwyg-editor/ui/source-view.esm.js +1 -1
  46. package/dist/src/nile-combobox/nile-combobox.css.js +28 -15
  47. package/dist/src/nile-combobox/nile-combobox.css.js.map +1 -1
  48. package/dist/src/nile-combobox/nile-combobox.js +49 -10
  49. package/dist/src/nile-combobox/nile-combobox.js.map +1 -1
  50. package/dist/src/nile-combobox/portal-manager.js +2 -22
  51. package/dist/src/nile-combobox/portal-manager.js.map +1 -1
  52. package/dist/src/nile-combobox/renderer.d.ts +2 -3
  53. package/dist/src/nile-combobox/renderer.js +14 -45
  54. package/dist/src/nile-combobox/renderer.js.map +1 -1
  55. package/dist/src/nile-empty-state/nile-empty-state.css.js +127 -4
  56. package/dist/src/nile-empty-state/nile-empty-state.css.js.map +1 -1
  57. package/dist/src/nile-empty-state/nile-empty-state.d.ts +35 -5
  58. package/dist/src/nile-empty-state/nile-empty-state.js +159 -42
  59. package/dist/src/nile-empty-state/nile-empty-state.js.map +1 -1
  60. package/dist/src/nile-empty-state/nile-empty-state.test.d.ts +1 -0
  61. package/dist/src/nile-empty-state/nile-empty-state.test.js +186 -15
  62. package/dist/src/nile-empty-state/nile-empty-state.test.js.map +1 -1
  63. package/dist/src/nile-pagination/nile-pagination.js +5 -5
  64. package/dist/src/nile-pagination/nile-pagination.js.map +1 -1
  65. package/dist/src/version.d.ts +1 -1
  66. package/dist/src/version.js +11 -3
  67. package/dist/src/version.js.map +1 -1
  68. package/dist/tsconfig.tsbuildinfo +1 -1
  69. package/dist/version.cjs.js +1 -1
  70. package/dist/version.cjs.js.map +1 -1
  71. package/dist/version.esm.js +1 -1
  72. package/dist/xml-09119317.cjs.js +2 -0
  73. package/dist/xml-09119317.cjs.js.map +1 -0
  74. package/dist/{xml-25c4108a.esm.js → xml-9bf771b3.esm.js} +1 -1
  75. package/package.json +2 -1
  76. package/scripts/replace-version.cjs +12 -10
  77. package/src/nile-combobox/nile-combobox.css.ts +28 -15
  78. package/src/nile-combobox/nile-combobox.ts +61 -12
  79. package/src/nile-combobox/portal-manager.ts +4 -22
  80. package/src/nile-combobox/renderer.ts +26 -69
  81. package/src/nile-empty-state/nile-empty-state.css.ts +127 -4
  82. package/src/nile-empty-state/nile-empty-state.test.ts +192 -15
  83. package/src/nile-empty-state/nile-empty-state.ts +195 -34
  84. package/src/nile-pagination/nile-pagination.ts +5 -5
  85. package/src/version.ts +11 -2
  86. package/vscode-html-custom-data.json +79 -16
  87. package/dist/xml-90fd974e.cjs.js +0 -2
  88. package/dist/xml-90fd974e.cjs.js.map +0 -1
@@ -71,11 +71,120 @@ export const styles = css`
71
71
  min-width:300px;
72
72
  max-width:512px;
73
73
  width:100%;
74
+ margin-inline: auto;
74
75
  height:max-content;
75
76
  display:flex;
76
77
  flex-direction:column;
77
78
  row-gap:var(--nile-es-gap);
78
79
  }
80
+ .empty-state--tone-info .empty-state__body--tonal {
81
+ background: linear-gradient(
82
+ 180deg,
83
+ var(--nile-colors-primary-100, var(--ng-colors-bg-brand-primary)) 0%,
84
+ var(--nile-colors-primary-400, var(--ng-colors-bg-brand-secondary)) 100%
85
+ );
86
+ }
87
+
88
+ .empty-state--tone-warning .empty-state__body--tonal {
89
+ background: linear-gradient(
90
+ 180deg,
91
+ var(--nile-colors-yellow-100, var(--ng-colors-bg-warning-primary)) 0%,
92
+ var(--nile-colors-yellow-400, var(--ng-colors-bg-warning-secondary)) 100%
93
+ );
94
+ }
95
+
96
+ .empty-state--tone-error .empty-state__body--tonal {
97
+ background: linear-gradient(
98
+ 180deg,
99
+ var(--nile-colors-red-100, var(--ng-colors-bg-error-primary)) 0%,
100
+ var(--nile-colors-red-400, var(--ng-colors-bg-error-secondary)) 100%
101
+ );
102
+ }
103
+
104
+ .empty-state--tone-info .nile-flat-type-icon {
105
+ background-color: var(--nile-colors-primary-100, var(--ng-colors-bg-brand-primary));
106
+ border-color: var(--nile-colors-primary-600, var(--ng-colors-border-brand));
107
+ }
108
+
109
+ .empty-state--tone-warning .nile-flat-type-icon {
110
+ background-color: var(--nile-colors-yellow-100, var(--ng-colors-bg-warning-primary));
111
+ border-color: var(--nile-colors-yellow-500, var(--ng-colors-border-warning));
112
+ }
113
+
114
+ .empty-state--tone-error .nile-flat-type-icon {
115
+ background-color: var(--nile-colors-red-100, var(--ng-colors-bg-error-primary));
116
+ border-color: var(--nile-colors-red-500, var(--ng-colors-border-error));
117
+ }
118
+
119
+ .empty-state--inline {
120
+ flex-direction: row;
121
+ align-items: center;
122
+ justify-content: center;
123
+ flex-wrap: wrap;
124
+ min-width: 0;
125
+ max-width: none;
126
+ column-gap: var(--nile-spacing-spacing-md, var(--ng-spacing-md));
127
+ row-gap: var(--nile-spacing-spacing-xs, var(--ng-spacing-xs));
128
+ padding: var(--nile-spacing-spacing-xl, var(--ng-spacing-xl))
129
+ var(--nile-spacing-spacing-lg, var(--ng-spacing-lg));
130
+ }
131
+
132
+ .empty-state--inline .empty-state__body {
133
+ flex-direction: row;
134
+ align-items: center;
135
+ column-gap: var(--nile-spacing-spacing-md, var(--ng-spacing-md));
136
+ }
137
+
138
+ .empty-state--inline .empty__state__text-section {
139
+ flex-direction: row;
140
+ align-items: baseline;
141
+ gap: var(--nile-spacing-spacing-sm, var(--ng-spacing-sm));
142
+ max-width: none;
143
+ }
144
+
145
+ .empty-state--inline .empty-state__text,
146
+ .empty-state--inline .empty-state__subtext {
147
+ font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));
148
+ line-height: var(--nile-line-height-small, var(--ng-line-height-text-sm));
149
+ text-align: left;
150
+ }
151
+ .empty-state__body--inline {
152
+ display: grid;
153
+ place-items: center;
154
+ }
155
+
156
+
157
+ .empty-state--page .empty-state__text {
158
+ font-size: var(--nile-type-scale-9, var(--ng-font-size-display-sm));
159
+ line-height: var(--nile-line-height-display, var(--ng-line-height-display-sm));
160
+ letter-spacing: -0.02em;
161
+ }
162
+
163
+ .empty-state--page .empty-state__subtext {
164
+ font-size: var(--nile-type-scale-4, var(--ng-font-size-text-md));
165
+ line-height: var(--nile-line-height-medium, var(--ng-line-height-text-md));
166
+ }
167
+
168
+ .empty-state--page .empty-state__code {
169
+ font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));
170
+ letter-spacing: 0.2em;
171
+ }
172
+ /* stack and page carry no padding of their own, so the dashed edge would sit
173
+ flush against the content — hence the roomy padding here. */
174
+ .empty-state--bordered {
175
+ border: 1px dashed var(--nile-colors-neutral-400, var(--ng-colors-border-primary));
176
+ border-radius: var(--nile-border-size-10, var(--ng-radius-xl));
177
+ background: var(--nile-colors-neutral-50, var(--ng-colors-bg-secondary));
178
+ padding: var(--nile-spacing-spacing-4xl, var(--ng-spacing-4xl))
179
+ var(--nile-spacing-spacing-3xl, var(--ng-spacing-3xl));
180
+ }
181
+
182
+ /* A strip stays tight when it is bordered too. Spelled out as its own
183
+ two-class rule so specificity decides, not declaration order. */
184
+ .empty-state--inline.empty-state--bordered {
185
+ padding: var(--nile-spacing-spacing-xl, var(--ng-spacing-xl))
186
+ var(--nile-spacing-spacing-lg, var(--ng-spacing-lg));
187
+ }
79
188
 
80
189
  .empty-state__body {
81
190
  display:flex;
@@ -90,13 +199,13 @@ export const styles = css`
90
199
  }
91
200
 
92
201
  .empty-state__img__icon {
93
- display: var(--ng-display-flex);
202
+ display: var(--nile-display-flex, var(--ng-display-flex));
94
203
  justify-content: center;
95
204
  align-items: center;
96
205
  }
97
206
 
98
207
  .empty-state__body--tonal {
99
- background: var(--nile-gradient-1, var(--ng-white-gradient-6));
208
+ background: var(--nile-gradient-1, var(--ng-white-gradient-6));
100
209
  width: 104px;
101
210
  height: 104px;
102
211
  border-radius: var(--nile-radius-full, var(--ng-radius-full));
@@ -109,8 +218,7 @@ export const styles = css`
109
218
  height:var(--nile-es-icon-size);
110
219
  aspect-ratio:1 / 1;
111
220
  border-radius: var(--nile-es-flat-option-border-radius);
112
-
113
- background-color: white;
221
+ background-color: var(--nile-colors-neutral-0, var(--ng-colors-bg-primary));
114
222
  display: grid;
115
223
  place-items: center;
116
224
  border: 1px solid var(--nile-colors-neutral-400, var(--ng-colors-border-primary));
@@ -121,6 +229,8 @@ export const styles = css`
121
229
  flex-direction:column;
122
230
  gap:var(--nile-es-text-gap);
123
231
  max-width:80%;
232
+ min-width: 0;
233
+ overflow-wrap: anywhere;
124
234
  }
125
235
 
126
236
  .empty-state__text {
@@ -145,7 +255,20 @@ export const styles = css`
145
255
  .empty-state-actions {
146
256
  display:flex;
147
257
  justify-content:center;
258
+ gap: var(--nile-spacing-spacing-md, var(--ng-spacing-md));
259
+ flex-wrap: wrap;
148
260
  }
261
+
262
+ .empty-state__code {
263
+ font-family: var(--nile-font-family-body, var(--ng-font-family-body));
264
+ font-size: var(--nile-type-scale-2, var(--ng-font-size-text-xs));
265
+ font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-medium));
266
+ letter-spacing: 0.14em;
267
+ text-transform: uppercase;
268
+ text-align: center;
269
+ color: var(--nile-colors-dark-400, var(--ng-colors-text-quaternary-500));
270
+ }
271
+
149
272
  `;
150
273
 
151
274
  export default [styles];
@@ -1,5 +1,9 @@
1
- import { expect, fixture, html } from '@open-wc/testing';
1
+ import { expect, fixture, html, waitUntil } from '@open-wc/testing';
2
2
  import './nile-empty-state';
3
+ // nile-glyph lives in its own package and is NOT registered by nile-elements —
4
+ // the component documents it as a @dependency. Register it here or every glyph
5
+ // assertion below would be querying an element that never upgrades.
6
+ import '@aquera/nile-glyph/nile-glyph';
3
7
  import type { NileEmptyState } from './nile-empty-state';
4
8
 
5
9
  describe('NileEmptyState', () => {
@@ -10,8 +14,7 @@ describe('NileEmptyState', () => {
10
14
  it('5. variant defaults tonal', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.variant).to.equal('tonal'); });
11
15
  it('6. text default', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.text).to.equal('Empty State'); });
12
16
  it('7. subText default', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.subText).to.equal('No Data'); });
13
- it('8. grayscale defaults false', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.grayscale).to.be.false; });
14
- it('9. svgIconSize defaults 40', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.svgIconSize).to.equal(40); });
17
+ it('9. svgIconSize unset by default', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.svgIconSize).to.be.undefined; });
15
18
  it('10. container part', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.shadowRoot!.querySelector('[part="container"]')).to.exist; });
16
19
  it('11. body part', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.shadowRoot!.querySelector('[part="body"]')).to.exist; });
17
20
  it('12. text part', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.shadowRoot!.querySelector('[part="text"]')).to.exist; });
@@ -27,9 +30,9 @@ describe('NileEmptyState', () => {
27
30
  it('22. flat body', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="flat"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('.empty-state__body--flat')).to.exist; });
28
31
  it('23. text displayed', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state text="Nothing here"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('.empty-state__text')!.textContent).to.contain('Nothing here'); });
29
32
  it('24. subText displayed', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state sub-text="Try again"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('.empty-state__subtext')!.textContent).to.contain('Try again'); });
30
- it('25. nile-icon in tonal', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="tonal"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('nile-icon')).to.exist; });
31
- it('26. nile-icon in content', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="content"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('nile-icon')).to.exist; });
32
- it('27. nile-icon in flat', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="flat"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('nile-icon')).to.exist; });
33
+ it('25. nile-glyph in tonal', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="tonal"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('nile-glyph')).to.exist; });
34
+ it('26. nile-glyph in content', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="content"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('nile-glyph')).to.exist; });
35
+ it('27. nile-glyph in flat', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="flat"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('nile-glyph')).to.exist; });
33
36
  it('28. slot', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.shadowRoot!.querySelector('slot')).to.exist; });
34
37
  it('29. slotted content', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state><button>Action</button></nile-empty-state>`); expect(el.querySelector('button')).to.exist; });
35
38
  it('30. dynamic text', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); el.text = 'New Text'; await el.updateComplete; expect(el.shadowRoot!.querySelector('.empty-state__text')!.textContent).to.contain('New Text'); });
@@ -62,26 +65,25 @@ describe('NileEmptyState', () => {
62
65
  it('57. localName', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.localName).to.equal('nile-empty-state'); });
63
66
  it('58. namespaceURI', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.namespaceURI).to.equal('http://www.w3.org/1999/xhtml'); });
64
67
  it('59. ownerDocument', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.ownerDocument).to.equal(document); });
65
- it('60. icon default', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.icon).to.contain('nile-icon-error'); });
68
+ it('60. icon default', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.icon).to.equal('ng-x-circle'); });
66
69
  it('61. custom icon', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state icon="star"></nile-empty-state>`); expect(el.icon).to.equal('star'); });
67
70
  it('62. actions class', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.shadowRoot!.querySelector('.empty-state-actions')).to.exist; });
68
71
  it('63. empty-state__body class', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.shadowRoot!.querySelector('.empty-state__body')).to.exist; });
69
72
  it('64. text-section class', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.shadowRoot!.querySelector('.empty__state__text-section')).to.exist; });
70
73
  it('65. sub-text attr', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state sub-text="Nope"></nile-empty-state>`); expect(el.subText).to.equal('Nope'); });
71
- it('66. tonal-body part', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="tonal"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('[part="tonal-body"]')).to.exist; });
74
+ it('66. tonal-body part', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="tonal"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('[part~="tonal-body"]')).to.exist; });
72
75
  it('67. content-image part', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="content"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('[part="content-image"]')).to.exist; });
73
76
  it('68. flat-body part', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="flat"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('[part="flat-body"]')).to.exist; });
74
77
  it('69. no tonal in flat', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="flat"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('.empty-state__body--tonal')).to.be.null; });
75
78
  it('70. no content in tonal', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="tonal"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('.empty-state__body--content')).to.be.null; });
76
79
  it('71. no flat in content', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="content"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('.empty-state__body--flat')).to.be.null; });
77
- it('72. icon-wrapper part content', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="content"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('[part="icon-wrapper"]')).to.exist; });
80
+ it('72. icon-wrapper part content', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="content"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('[part~="icon-wrapper"]')).to.exist; });
78
81
  it('73. classList add', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); el.classList.add('z'); expect(el.classList.contains('z')).to.be.true; });
79
82
  it('74. dataset', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state data-idx="0"></nile-empty-state>`); expect(el.dataset.idx).to.equal('0'); });
80
83
  it('75. getBoundingClientRect', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.getBoundingClientRect()).to.exist; });
81
84
  it('76. size type string', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(typeof el.size).to.equal('string'); });
82
85
  it('77. variant type string', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(typeof el.variant).to.equal('string'); });
83
- it('78. grayscale type boolean', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(typeof el.grayscale).to.equal('boolean'); });
84
- it('79. svgIconSize type number', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(typeof el.svgIconSize).to.equal('number'); });
86
+ it('79. svgIconSize type number once set', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state .svgIconSize=${60}></nile-empty-state>`); expect(typeof el.svgIconSize).to.equal('number'); });
85
87
  it('80. createElement', async () => { const el = document.createElement('nile-empty-state') as NileEmptyState; document.body.appendChild(el); await el.updateComplete; expect(el.shadowRoot).to.not.be.null; document.body.removeChild(el); });
86
88
  it('81. aria-label', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state aria-label="Empty"></nile-empty-state>`); expect(el.getAttribute('aria-label')).to.equal('Empty'); });
87
89
  it('82. role', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state role="status"></nile-empty-state>`); expect(el.getAttribute('role')).to.equal('status'); });
@@ -98,9 +100,184 @@ describe('NileEmptyState', () => {
98
100
  it('93. dispatchCustomEvent', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); let d: any; el.addEventListener('t', ((e: CustomEvent) => { d = e.detail; }) as EventListener); el.dispatchEvent(new CustomEvent('t', { detail: 'x' })); expect(d).to.equal('x'); });
99
101
  it('94. scrollIntoView', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.scrollIntoView).to.be.a('function'); });
100
102
  it('95. focus method', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.focus).to.be.a('function'); });
101
- it('96. dynamic icon', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="tonal"></nile-empty-state>`); el.icon = 'star'; await el.updateComplete; const icon = el.shadowRoot!.querySelector('nile-icon'); expect(icon!.getAttribute('name')).to.equal('star'); });
102
- it('97. svgIconUrl renders custom', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="tonal" svgIconUrl="/path/icon.svg"></nile-empty-state>`); await el.updateComplete; const icon = el.shadowRoot!.querySelector('nile-icon'); expect(icon!.getAttribute('customsvgpath')).to.exist; });
103
- it('98. svgIconSize applies', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="tonal" svgIconUrl="/p.svg" .svgIconSize=${60}></nile-empty-state>`); await el.updateComplete; const icon = el.shadowRoot!.querySelector('nile-icon'); expect(icon!.getAttribute('size')).to.equal('60'); });
103
+ it('96. dynamic icon', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="tonal"></nile-empty-state>`); el.icon = 'star'; await el.updateComplete; const icon = el.shadowRoot!.querySelector('nile-glyph'); expect(icon!.getAttribute('name')).to.equal('star'); });
104
+ it('97. svgIconUrl renders custom', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="tonal" svgIconUrl="/path/icon.svg"></nile-empty-state>`); await el.updateComplete; const icon = el.shadowRoot!.querySelector('nile-glyph'); expect(icon!.getAttribute('customsvgpath')).to.exist; });
105
+ it('98. svgIconSize applies', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="tonal" svgIconUrl="/p.svg" .svgIconSize=${60}></nile-empty-state>`); await el.updateComplete; const icon = el.shadowRoot!.querySelector('nile-glyph'); expect(icon!.getAttribute('size')).to.equal('60'); });
104
106
  it('99. shadow childNodes', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.shadowRoot!.childNodes.length).to.be.greaterThan(0); });
105
- it('100. full integration', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state text="Empty" sub-text="Nothing" variant="content" size="lg" icon="star" class="e" id="e1"><button>Retry</button></nile-empty-state>`); expect(el.text).to.equal('Empty'); expect(el.subText).to.equal('Nothing'); expect(el.variant).to.equal('content'); expect(el.size).to.equal('lg'); expect(el.icon).to.equal('star'); expect(el.id).to.equal('e1'); expect(el.querySelector('button')).to.exist; expect(el.shadowRoot!.querySelector('.empty-state--lg')).to.exist; });
107
+ it('100. legacy full integration', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state text="Empty" sub-text="Nothing" variant="content" size="lg" icon="star" class="e" id="e1"><button>Retry</button></nile-empty-state>`); expect(el.text).to.equal('Empty'); expect(el.subText).to.equal('Nothing'); expect(el.variant).to.equal('content'); expect(el.size).to.equal('lg'); expect(el.icon).to.equal('star'); expect(el.id).to.equal('e1'); expect(el.querySelector('button')).to.exist; expect(el.shadowRoot!.querySelector('.empty-state--lg')).to.exist; });
108
+
109
+ // ── status presets ────────────────────────────────────────────────────────
110
+
111
+ const container = (el: NileEmptyState) => el.shadowRoot!.querySelector('[part="container"]')!;
112
+ const glyph = (el: NileEmptyState) => el.shadowRoot!.querySelector('nile-glyph')!;
113
+
114
+ it('101. status unset by default', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.status).to.be.undefined; });
115
+ it('102. status=empty copy', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="empty"></nile-empty-state>`); expect(el.text).to.equal('No data yet'); expect(el.subText).to.contain('Rows appear here'); });
116
+ it('103. status=empty icon', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="empty"></nile-empty-state>`); expect(el.icon).to.equal('ng-database'); });
117
+ it('104. status=no-results copy', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="no-results"></nile-empty-state>`); expect(el.text).to.equal('No matches'); expect(el.icon).to.equal('ng-search-lg'); });
118
+ it('105. status=not-found copy', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="not-found"></nile-empty-state>`); expect(el.text).to.contain(`couldn't find that`); expect(el.icon).to.equal('ng-file-06'); });
119
+ it('106. status=no-access copy', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="no-access"></nile-empty-state>`); expect(el.text).to.contain(`don't have access`); expect(el.icon).to.equal('ng-lock'); });
120
+ it('107. status=error copy', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="error"></nile-empty-state>`); expect(el.text).to.equal('Something went wrong'); expect(el.icon).to.equal('ng-alert-circle'); });
121
+ it('108. status=offline copy', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="offline"></nile-empty-state>`); expect(el.text).to.equal('Connection lost'); expect(el.icon).to.equal('ng-cloud-off'); });
122
+ it('109. preset icon reaches the glyph', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="empty"></nile-empty-state>`); expect(glyph(el).getAttribute('name')).to.equal('ng-database'); });
123
+ it('110. preset renders the copy', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="no-results"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('.empty-state__text')!.textContent).to.contain('No matches'); });
124
+ it('111. status reflects', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="error"></nile-empty-state>`); expect(el.getAttribute('status')).to.equal('error'); });
125
+ it('112. status change reruns the preset', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="empty"></nile-empty-state>`); el.status = 'offline'; await el.updateComplete; expect(el.text).to.equal('Connection lost'); });
126
+
127
+ // ── per-property overrides beat the preset ────────────────────────────────
128
+
129
+ it('113. text overrides preset', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="empty" text="Mine"></nile-empty-state>`); expect(el.text).to.equal('Mine'); expect(el.subText).to.contain('Rows appear here'); });
130
+ it('114. sub-text overrides preset', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="empty" sub-text="Mine"></nile-empty-state>`); expect(el.subText).to.equal('Mine'); expect(el.text).to.equal('No data yet'); });
131
+ it('115. icon overrides preset', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="empty" icon="star"></nile-empty-state>`); expect(el.icon).to.equal('star'); });
132
+ it('116. override survives a status change', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="empty" text="Mine"></nile-empty-state>`); el.status = 'error'; await el.updateComplete; expect(el.text).to.equal('Mine'); });
133
+
134
+ // ── tone ──────────────────────────────────────────────────────────────────
135
+
136
+ it('117. tone defaults neutral without status', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.tone).to.equal('neutral'); });
137
+ it('118. tone defaults from status', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="no-access"></nile-empty-state>`); expect(el.tone).to.equal('warning'); });
138
+ it('119. error status is error tone', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="error"></nile-empty-state>`); expect(el.tone).to.equal('error'); });
139
+ it('120. tone overridable', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="no-access" tone="neutral"></nile-empty-state>`); expect(el.tone).to.equal('neutral'); });
140
+ it('121. tone class on container', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="error"></nile-empty-state>`); expect(container(el).classList.contains('empty-state--tone-error')).to.be.true; });
141
+ it('122. neutral tone omits glyph color', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="empty"></nile-empty-state>`); expect(glyph(el).getAttribute('color')).to.be.null; });
142
+ it('123. warning tone sets glyph color', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="not-found"></nile-empty-state>`); expect(glyph(el).getAttribute('color')).to.contain('fg-warning-primary'); });
143
+
144
+ // ── ARIA ──────────────────────────────────────────────────────────────────
145
+
146
+ it('124. no live region without status', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(container(el).getAttribute('role')).to.be.null; expect(container(el).getAttribute('aria-live')).to.be.null; });
147
+ it('125. status gets role=status/polite', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="empty"></nile-empty-state>`); expect(container(el).getAttribute('role')).to.equal('status'); expect(container(el).getAttribute('aria-live')).to.equal('polite'); });
148
+ it('126. error gets role=alert/assertive', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="error"></nile-empty-state>`); expect(container(el).getAttribute('role')).to.equal('alert'); expect(container(el).getAttribute('aria-live')).to.equal('assertive'); });
149
+ it('127. offline gets role=alert', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="offline"></nile-empty-state>`); expect(container(el).getAttribute('role')).to.equal('alert'); });
150
+ it('128. no-access does not interrupt', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="no-access"></nile-empty-state>`); expect(container(el).getAttribute('aria-live')).to.equal('polite'); });
151
+
152
+ // ── layout ────────────────────────────────────────────────────────────────
153
+
154
+ it('129. layout defaults stack', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.layout).to.equal('stack'); expect(container(el).classList.contains('empty-state--stack')).to.be.true; });
155
+ it('130. layout=inline class', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state layout="inline"></nile-empty-state>`); expect(container(el).classList.contains('empty-state--inline')).to.be.true; });
156
+ it('131. layout=page class', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state layout="page"></nile-empty-state>`); expect(container(el).classList.contains('empty-state--page')).to.be.true; });
157
+ it('132. layout reflects', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state layout="page"></nile-empty-state>`); expect(el.getAttribute('layout')).to.equal('page'); });
158
+ it('133. inline shrinks the glyph to 20', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state layout="inline"></nile-empty-state>`); expect(glyph(el).getAttribute('size')).to.equal('20'); });
159
+ it('134. stack keeps the 40px tonal glyph', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(glyph(el).getAttribute('size')).to.equal('40'); });
160
+ it('135. inline drops the 512px ceiling', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state layout="inline"></nile-empty-state>`); expect(getComputedStyle(container(el)).maxWidth).to.equal('none'); });
161
+ it('136. stack keeps the 512px ceiling', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(getComputedStyle(container(el)).maxWidth).to.equal('512px'); });
162
+ it('137. dynamic layout', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); el.layout = 'inline'; await el.updateComplete; expect(container(el).classList.contains('empty-state--inline')).to.be.true; });
163
+
164
+ // ── code ──────────────────────────────────────────────────────────────────
165
+
166
+ it('138. no code part when unset', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.shadowRoot!.querySelector('[part="code"]')).to.be.null; });
167
+ it('139. code renders', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state code="Error 404"></nile-empty-state>`); expect(el.shadowRoot!.querySelector('[part="code"]')!.textContent).to.contain('Error 404'); });
168
+ it('140. code sits above the title', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state code="404"></nile-empty-state>`); const section = el.shadowRoot!.querySelector('[part="text-section"]')!; expect(section.firstElementChild!.getAttribute('part')).to.equal('code'); });
169
+ it('141. dynamic code', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); el.code = '500'; await el.updateComplete; expect(el.shadowRoot!.querySelector('[part="code"]')).to.exist; });
170
+
171
+ // ── bordered ──────────────────────────────────────────────────────────────
172
+
173
+ it('147. bordered defaults false', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(el.bordered).to.be.false; expect(container(el).classList.contains('empty-state--bordered')).to.be.false; });
174
+ it('148. bordered class', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state bordered></nile-empty-state>`); expect(container(el).classList.contains('empty-state--bordered')).to.be.true; });
175
+ // The --ng-* sheet isn't loaded here, so the token has to be supplied or the
176
+ // whole `border` shorthand is invalid at computed-value time and drops out.
177
+ it('149. bordered draws a dashed edge', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state bordered style="--ng-colors-border-primary:#D5D7DA"></nile-empty-state>`); expect(getComputedStyle(container(el)).borderTopStyle).to.equal('dashed'); });
178
+ it('150. bordered reflects', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state bordered></nile-empty-state>`); expect(el.hasAttribute('bordered')).to.be.true; });
179
+
180
+ // --bordered and --inline both set padding at one-class specificity, so on their
181
+ // own the winner would be whichever rule comes last in the stylesheet. The explicit
182
+ // .empty-state--inline.empty-state--bordered rule settles it; these pin that intent.
183
+ const SPACING = '--ng-spacing-xl:20px;--ng-spacing-lg:16px;--ng-spacing-4xl:96px;--ng-spacing-3xl:64px';
184
+ it('150b. bordered stack uses the roomy padding', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state bordered style=${SPACING}></nile-empty-state>`); const cs = getComputedStyle(container(el)); expect(cs.paddingTop).to.equal('96px'); expect(cs.paddingLeft).to.equal('64px'); });
185
+ it('150c. bordered inline keeps the tight padding', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state layout="inline" bordered style=${SPACING}></nile-empty-state>`); const cs = getComputedStyle(container(el)); expect(cs.paddingTop).to.equal('20px'); expect(cs.paddingLeft).to.equal('16px'); });
186
+ it('150d. bordered does not change inline padding', async () => { const plain = await fixture<NileEmptyState>(html`<nile-empty-state layout="inline" style=${SPACING}></nile-empty-state>`); const edged = await fixture<NileEmptyState>(html`<nile-empty-state layout="inline" bordered style=${SPACING}></nile-empty-state>`); expect(getComputedStyle(container(edged)).padding).to.equal(getComputedStyle(container(plain)).padding); });
187
+ it('150e. bordered inline still draws the dashed edge', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state layout="inline" bordered style="--ng-colors-border-primary:#D5D7DA"></nile-empty-state>`); expect(getComputedStyle(container(el)).borderTopStyle).to.equal('dashed'); });
188
+
189
+ // ── regressions the specimen sheet flagged ────────────────────────────────
190
+
191
+ it('151. content variant no longer forces white', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="content"></nile-empty-state>`); expect(glyph(el).getAttribute('color')).to.not.equal('white'); });
192
+ // Was a literal `white`, which punched a white chip into any dark page. Feed
193
+ // the theme token a sentinel: if the chip picks it up, it is no longer fixed.
194
+ it('152. flat chip background tracks the theme token', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="flat" style="--ng-colors-bg-primary:rgb(1, 2, 3)"></nile-empty-state>`); const chip = el.shadowRoot!.querySelector('.nile-flat-type-icon')!; expect(getComputedStyle(chip).backgroundColor).to.equal('rgb(1, 2, 3)'); });
195
+ it('153. svgIconUrl still wins over a preset icon', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="error" svgIconUrl="/p.svg"></nile-empty-state>`); expect(glyph(el).getAttribute('customsvgpath')).to.exist; expect(glyph(el).getAttribute('name')).to.be.null; });
196
+ it('154. svgIconColor beats the tone colour', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="error" svgIconUrl="/p.svg" svgIconColor="#123456"></nile-empty-state>`); expect(glyph(el).getAttribute('color')).to.equal('#123456'); });
197
+ // ── the preset glyphs must actually exist ─────────────────────────────────
198
+
199
+ // nile-glyph strips a name's dashes, looks it up, and silently renders nothing
200
+ // when it misses — so a bad preset name is invisible rather than loud. These
201
+ // assert the glyph ACTUALLY DRAWS, which is stronger than checking the name
202
+ // against a barrel: it exercises the same resolution path the browser takes,
203
+ // and it cannot drift from it.
204
+ const STATUSES = ['empty', 'no-results', 'not-found', 'no-access', 'error', 'offline'] as const;
205
+
206
+ /** The <svg> nile-glyph drew, once it has resolved the name. */
207
+ const drawnSvg = async (el: NileEmptyState): Promise<SVGElement> => {
208
+ const g = glyph(el);
209
+ await waitUntil(
210
+ () => !!g.shadowRoot?.querySelector('svg'),
211
+ `glyph "${el.icon}" never drew — the name does not resolve in @aquera/nile-glyph`,
212
+ );
213
+ return g.shadowRoot!.querySelector('svg')!;
214
+ };
215
+
216
+ STATUSES.forEach((status, i) => {
217
+ it(`${155 + i}. status=${status} names a glyph that actually draws`, async () => {
218
+ const el = await fixture<NileEmptyState>(html`<nile-empty-state status="${status}"></nile-empty-state>`);
219
+ expect(await drawnSvg(el)).to.exist;
220
+ });
221
+ });
222
+
223
+ // nile-glyph resolves the var expression and reflects the resolved value back,
224
+ // so these assert the paint method that actually reaches the SVG. Every glyph the
225
+ // component draws is stroked now — preset or not, either theme.
226
+ it('161. preset glyphs are stroked, not filled', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="empty"></nile-empty-state>`); expect(glyph(el).getAttribute('method')).to.equal('stroke'); });
227
+ it('162. preset glyph artwork is an outline', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="empty"></nile-empty-state>`); expect((await drawnSvg(el)).getAttribute('fill')).to.equal('none'); });
228
+ it('163. a call site with no status is stroked too', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); expect(glyph(el).getAttribute('method')).to.equal('stroke'); });
229
+ it('163b. content variant strokes a preset glyph', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="content" status="empty"></nile-empty-state>`); expect(glyph(el).getAttribute('method')).to.equal('stroke'); });
230
+ it('163c. content variant strokes a caller icon as well', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="content" icon="star"></nile-empty-state>`); expect(glyph(el).getAttribute('method')).to.equal('stroke'); });
231
+ it('164. overriding the preset icon does not change the paint method', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="empty" icon="star"></nile-empty-state>`); expect(glyph(el).getAttribute('method')).to.equal('stroke'); });
232
+
233
+ // ── nile-glyph + ::part(icon-chrome) ──────────────────────────────────────
234
+ //
235
+ // The component draws through nile-glyph, not nile-icon: nile-icon resolves
236
+ // against a vendored subset and renders nothing when a name misses it, which
237
+ // is what held the presets on near-miss names.
238
+
239
+ const SHAPES: ReadonlyArray<readonly [string, string]> = [
240
+ ['variant', 'tonal'], ['variant', 'flat'], ['variant', 'content'], ['layout', 'inline'],
241
+ ];
242
+ const partsOf = (el: NileEmptyState, sel: string) =>
243
+ (el.shadowRoot!.querySelector(sel)!.getAttribute('part') ?? '').split(/\s+/);
244
+
245
+ it('166. never renders a nile-icon in any shape', async () => { for (const [name, value] of SHAPES) { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); el.setAttribute(name, value); await el.updateComplete; expect(el.shadowRoot!.querySelector('nile-icon'), `${name}="${value}"`).to.be.null; } });
246
+ it('167. renders exactly one glyph per shape', async () => { for (const [name, value] of SHAPES) { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); el.setAttribute(name, value); await el.updateComplete; expect(el.shadowRoot!.querySelectorAll('[part="body"] nile-glyph').length, `${name}="${value}"`).to.equal(1); } });
247
+ it('169. every shape exposes exactly one icon-chrome element', async () => { for (const [name, value] of SHAPES) { const el = await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`); el.setAttribute(name, value); await el.updateComplete; expect(el.shadowRoot!.querySelectorAll('[part~="icon-chrome"]').length, `${name}="${value}"`).to.equal(1); } });
248
+ it('170. tonal disc carries icon-chrome alongside tonal-body', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="tonal"></nile-empty-state>`); const p = partsOf(el, '.empty-state__body--tonal'); expect(p).to.include('icon-chrome'); expect(p).to.include('tonal-body'); });
249
+ it('171. flat tile carries icon-chrome, its wrapper does not', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="flat"></nile-empty-state>`); expect(partsOf(el, '.nile-flat-type-icon')).to.include('icon-chrome'); expect(partsOf(el, '.empty-state__body--flat')).to.not.include('icon-chrome'); });
250
+ it('172. content icon box carries icon-chrome alongside icon-wrapper', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="content"></nile-empty-state>`); const p = partsOf(el, '.empty-state__img__icon'); expect(p).to.include('icon-chrome'); expect(p).to.include('icon-wrapper'); });
251
+ it('173. inline exposes a bare glyph box, no variant chrome', async () => { for (const v of ['tonal', 'flat', 'content'] as const) { const el = await fixture<NileEmptyState>(html`<nile-empty-state layout="inline" variant=${v}></nile-empty-state>`); const p = partsOf(el, '.empty-state__body--inline'); expect(p, v).to.include('inline-icon'); expect(p, v).to.include('icon-chrome'); expect(el.shadowRoot!.querySelector('.empty-state__body--tonal'), v).to.be.null; expect(el.shadowRoot!.querySelector('.nile-flat-type-icon'), v).to.be.null; expect(el.shadowRoot!.querySelector('.empty-state__img__icon'), v).to.be.null; } });
252
+ it('174. inline still honours the status preset', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state layout="inline" status="no-results"></nile-empty-state>`); expect(glyph(el).getAttribute('name')).to.equal('ng-search-lg'); expect(container(el).getAttribute('role')).to.equal('status'); });
253
+ it('175. the inert nile-icon variant attribute is gone from the flat tile', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="flat"></nile-empty-state>`); expect(glyph(el).hasAttribute('variant')).to.be.false; });
254
+
255
+ // ── svgIconColor beats the tone, so chrome and glyph can differ ───────────
256
+
257
+ it('176. svgIconColor colours a catalogue glyph', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state icon="ng-search-lg" svgIconColor="#6941C6"></nile-empty-state>`); expect(glyph(el).getAttribute('color')).to.equal('#6941C6'); });
258
+ it('177. svgIconColor beats the tone colour', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="error" svgIconColor="#6941C6"></nile-empty-state>`); expect(el.tone).to.equal('error'); expect(glyph(el).getAttribute('color')).to.equal('#6941C6'); });
259
+ it('178. without svgIconColor the tone still decides', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="error"></nile-empty-state>`); expect(glyph(el).getAttribute('color')).to.contain('--ng-colors-fg-error-primary'); });
260
+ it('179. svgIconColor applies in every shape', async () => { for (const [name, value] of SHAPES) { const el = await fixture<NileEmptyState>(html`<nile-empty-state svgIconColor="#6941C6"></nile-empty-state>`); el.setAttribute(name, value); await el.updateComplete; expect(glyph(el).getAttribute('color'), `${name}="${value}"`).to.equal('#6941C6'); } });
261
+
262
+ // The resolving accessors must report a resolved oldValue: Lit reads the new value
263
+ // back through the getter, so capturing the raw backing field would compare an
264
+ // undefined against a resolved value.
265
+ it('180. assigning the already-resolved value schedules nothing', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="empty"></nile-empty-state>`); el.icon = el.icon; expect((el as any).isUpdatePending).to.be.false; });
266
+ it('181. same for text and tone', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="error"></nile-empty-state>`); el.text = el.text; el.tone = el.tone; expect((el as any).isUpdatePending).to.be.false; });
267
+ it('182. a real change still schedules', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="empty"></nile-empty-state>`); el.icon = 'star'; expect((el as any).isUpdatePending).to.be.true; await el.updateComplete; expect(glyph(el).getAttribute('name')).to.equal('star'); });
268
+ it('183. changedProperties reports the resolved old value', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="empty"></nile-empty-state>`); let seen: unknown = 'unset'; (el as any).willUpdate = (c: Map<string, unknown>) => { if (c.has('icon')) seen = c.get('icon'); }; el.icon = 'star'; await el.updateComplete; expect(seen).to.equal('ng-database'); });
269
+ it('184. removing the attribute falls back to the preset', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="empty" icon="star"></nile-empty-state>`); el.removeAttribute('icon'); await el.updateComplete; expect(el.icon).to.equal('ng-database'); });
270
+
271
+ // svgIconSize overrides the layout-driven size rather than replacing it, so a
272
+ // custom SVG tracks the shape it is drawn in unless the caller says otherwise.
273
+ it('185. custom SVG follows the inline size', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state layout="inline" svgIconUrl="/p.svg"></nile-empty-state>`); expect(glyph(el).getAttribute('size')).to.equal('20'); });
274
+ it('186. custom SVG follows the tonal size', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="tonal" svgIconUrl="/p.svg"></nile-empty-state>`); expect(glyph(el).getAttribute('size')).to.equal('40'); });
275
+ it('187. custom SVG follows the flat size', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="flat" svgIconUrl="/p.svg"></nile-empty-state>`); expect(glyph(el).getAttribute('size')).to.equal('24'); });
276
+ it('188. custom SVG follows the flat lg size', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="flat" size="lg" svgIconUrl="/p.svg"></nile-empty-state>`); expect(glyph(el).getAttribute('size')).to.equal('28'); });
277
+ it('189. custom SVG follows the content size', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="content" svgIconUrl="/p.svg"></nile-empty-state>`); expect(glyph(el).getAttribute('size')).to.equal('24'); });
278
+ it('190. svgIconSize overrides the layout size', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state layout="inline" svgIconUrl="/p.svg" .svgIconSize=${60}></nile-empty-state>`); expect(glyph(el).getAttribute('size')).to.equal('60'); });
279
+ it('191. svgIconSize does not affect a catalogue glyph', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state layout="inline" .svgIconSize=${60}></nile-empty-state>`); expect(glyph(el).getAttribute('size')).to.equal('20'); });
280
+
281
+
282
+ it('165. full new-API integration', async () => { const el = await fixture<NileEmptyState>(html`<nile-empty-state status="not-found" layout="page" size="lg" code="Error 404" bordered><button>Go back</button></nile-empty-state>`); expect(el.text).to.contain(`couldn't find that`); expect(el.tone).to.equal('warning'); expect(container(el).getAttribute('role')).to.equal('status'); expect(container(el).classList.contains('empty-state--page')).to.be.true; expect(container(el).classList.contains('empty-state--bordered')).to.be.true; expect(el.shadowRoot!.querySelector('[part="code"]')!.textContent).to.contain('Error 404'); expect(el.querySelector('button')).to.exist; });
106
283
  });