@aquera/nile-elements 2.0.10 → 2.0.12

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 (104) hide show
  1. package/README.md +6 -0
  2. package/demo/app-shell.html +852 -0
  3. package/demo/nxtgen-classes.css +2 -2
  4. package/demo/nxtgen-properties.css +2 -2
  5. package/demo/nxtgen-utilities.css +2 -2
  6. package/demo/nxtgen.css +2 -2
  7. package/demo/utilities.css +2 -2
  8. package/demo/variables.css +2 -2
  9. package/dist/index.cjs.js +1 -1
  10. package/dist/index.esm.js +1 -1
  11. package/dist/index.js +595 -302
  12. package/dist/internal/app-shell-utility-control-styles.cjs.js +2 -0
  13. package/dist/internal/app-shell-utility-control-styles.cjs.js.map +1 -0
  14. package/dist/internal/app-shell-utility-control-styles.esm.js +16 -0
  15. package/dist/nile-app-shell/index.cjs.js +1 -1
  16. package/dist/nile-app-shell/index.esm.js +1 -1
  17. package/dist/nile-app-shell/nile-app-shell.cjs.js +1 -1
  18. package/dist/nile-app-shell/nile-app-shell.cjs.js.map +1 -1
  19. package/dist/nile-app-shell/nile-app-shell.css.cjs.js +1 -1
  20. package/dist/nile-app-shell/nile-app-shell.css.cjs.js.map +1 -1
  21. package/dist/nile-app-shell/nile-app-shell.css.esm.js +277 -96
  22. package/dist/nile-app-shell/nile-app-shell.esm.js +67 -11
  23. package/dist/nile-app-shell/shell-controller.cjs.js.map +1 -1
  24. package/dist/nile-app-shell-main/nile-app-shell-main.cjs.js.map +1 -1
  25. package/dist/nile-app-shell-nav-toggle/index.cjs.js +1 -1
  26. package/dist/nile-app-shell-nav-toggle/index.esm.js +1 -1
  27. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.cjs.js +1 -1
  28. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.cjs.js.map +1 -1
  29. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.cjs.js +1 -1
  30. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.cjs.js.map +1 -1
  31. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.esm.js +7 -3
  32. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.esm.js +3 -3
  33. package/dist/nile-app-shell-panel/index.cjs.js +1 -1
  34. package/dist/nile-app-shell-panel/index.esm.js +1 -1
  35. package/dist/nile-app-shell-panel/nile-app-shell-panel.cjs.js +1 -1
  36. package/dist/nile-app-shell-panel/nile-app-shell-panel.cjs.js.map +1 -1
  37. package/dist/nile-app-shell-panel/nile-app-shell-panel.css.cjs.js +1 -1
  38. package/dist/nile-app-shell-panel/nile-app-shell-panel.css.cjs.js.map +1 -1
  39. package/dist/nile-app-shell-panel/nile-app-shell-panel.css.esm.js +32 -8
  40. package/dist/nile-app-shell-panel/nile-app-shell-panel.esm.js +18 -5
  41. package/dist/nile-app-shell-side-nav/nile-app-shell-side-nav.cjs.js.map +1 -1
  42. package/dist/nile-app-shell-side-nav/nile-app-shell-side-nav.css.cjs.js.map +1 -1
  43. package/dist/src/internal/app-shell-utility-control-styles.d.ts +12 -0
  44. package/dist/src/internal/app-shell-utility-control-styles.js +29 -0
  45. package/dist/src/internal/app-shell-utility-control-styles.js.map +1 -0
  46. package/dist/src/nile-app-shell/nile-app-shell-regions.test.d.ts +3 -0
  47. package/dist/src/nile-app-shell/nile-app-shell-regions.test.js +210 -0
  48. package/dist/src/nile-app-shell/nile-app-shell-regions.test.js.map +1 -1
  49. package/dist/src/nile-app-shell/nile-app-shell.css.d.ts +1 -9
  50. package/dist/src/nile-app-shell/nile-app-shell.css.js +277 -103
  51. package/dist/src/nile-app-shell/nile-app-shell.css.js.map +1 -1
  52. package/dist/src/nile-app-shell/nile-app-shell.d.ts +90 -122
  53. package/dist/src/nile-app-shell/nile-app-shell.js +235 -144
  54. package/dist/src/nile-app-shell/nile-app-shell.js.map +1 -1
  55. package/dist/src/nile-app-shell/nile-app-shell.test.d.ts +4 -0
  56. package/dist/src/nile-app-shell/nile-app-shell.test.js +1116 -29
  57. package/dist/src/nile-app-shell/nile-app-shell.test.js.map +1 -1
  58. package/dist/src/nile-app-shell/shell-controller.d.ts +1 -8
  59. package/dist/src/nile-app-shell/shell-controller.js +2 -14
  60. package/dist/src/nile-app-shell/shell-controller.js.map +1 -1
  61. package/dist/src/nile-app-shell-main/nile-app-shell-main.d.ts +2 -6
  62. package/dist/src/nile-app-shell-main/nile-app-shell-main.js +2 -6
  63. package/dist/src/nile-app-shell-main/nile-app-shell-main.js.map +1 -1
  64. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.d.ts +1 -4
  65. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.js +7 -5
  66. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.js.map +1 -1
  67. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.d.ts +4 -8
  68. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.js +5 -6
  69. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.js.map +1 -1
  70. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.css.js +31 -6
  71. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.css.js.map +1 -1
  72. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.d.ts +18 -13
  73. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.js +45 -16
  74. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.js.map +1 -1
  75. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.css.d.ts +1 -4
  76. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.css.js +1 -4
  77. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.css.js.map +1 -1
  78. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.d.ts +4 -12
  79. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.js +4 -12
  80. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.js.map +1 -1
  81. package/dist/src/version.js +1 -1
  82. package/dist/src/version.js.map +1 -1
  83. package/dist/tsconfig.tsbuildinfo +1 -1
  84. package/dist/version.cjs.js +1 -1
  85. package/dist/version.cjs.js.map +1 -1
  86. package/dist/version.esm.js +1 -1
  87. package/package.json +1 -1
  88. package/plop-templates/lit/lit.test.ts.hbs +22 -0
  89. package/plop-templates/lit/lit.ts.hbs +10 -5
  90. package/plopfile.js +12 -0
  91. package/src/internal/app-shell-utility-control-styles.ts +30 -0
  92. package/src/nile-app-shell/nile-app-shell-regions.test.ts +252 -0
  93. package/src/nile-app-shell/nile-app-shell.css.ts +277 -103
  94. package/src/nile-app-shell/nile-app-shell.test.ts +1363 -29
  95. package/src/nile-app-shell/nile-app-shell.ts +249 -149
  96. package/src/nile-app-shell/shell-controller.ts +2 -14
  97. package/src/nile-app-shell-main/nile-app-shell-main.ts +2 -6
  98. package/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.ts +7 -5
  99. package/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.ts +7 -11
  100. package/src/nile-app-shell-panel/nile-app-shell-panel.css.ts +31 -6
  101. package/src/nile-app-shell-panel/nile-app-shell-panel.ts +44 -16
  102. package/src/nile-app-shell-side-nav/nile-app-shell-side-nav.css.ts +1 -4
  103. package/src/nile-app-shell-side-nav/nile-app-shell-side-nav.ts +4 -12
  104. package/vscode-html-custom-data.json +85 -18
@@ -22,6 +22,11 @@ export type NileAppShellTopBarPlacement = 'content' | 'shell';
22
22
  /** How the main region lays its content out. */
23
23
  export type NileAppShellContentAlign = 'fluid' | 'centered';
24
24
 
25
+ /** The `detail` of the `nile-click` the header back control emits. */
26
+ export interface NileAppShellClickDetail {
27
+ value: 'back';
28
+ }
29
+
25
30
  /** The shell's own track and region widths, in px. */
26
31
  interface ShellGeometry {
27
32
  nav: number;
@@ -33,9 +38,7 @@ interface ShellGeometry {
33
38
  }
34
39
 
35
40
  /**
36
- * @summary The application frame every route inherits: a side nav, a content
37
- * container, an optional aside and an AI assistant panel. It holds anything —
38
- * the shell owns the boxes, the page owns what goes in them.
41
+ * @summary The application frame every route inherits: a side nav, a content container, an optional aside and an AI assistant panel.
39
42
  *
40
43
  * @status stable
41
44
  * @since 2.0
@@ -47,29 +50,22 @@ interface ShellGeometry {
47
50
  *
48
51
  * @slot - The page body, inside the content container.
49
52
  * @slot banner - A full-width strip above every other region.
50
- * @slot top-bar - The page-title bar. Mounted inside the container by default,
51
- * or spanning the shell when `top-bar-placement="shell"`. The nav toggle is
52
- * rendered before whatever is slotted here.
53
+ * @slot top-bar - The page-title bar, mounted inside the container by default or spanning the shell when `top-bar-placement="shell"`.
53
54
  * @slot top-bar-actions - Controls pinned to the end of the top bar.
54
- * @slot tabs - A hoisted second-level tab strip, flush under the top bar and
55
- * outside `<main>`'s scroller, so it never scrolls away. The region carries
56
- * the strip's height, padding and rule, so slot a `<nile-tab-group>` or the
57
- * tab controls themselves.
55
+ * @slot tabs - A hoisted second-level tab strip, flush under the top bar and outside `<main>`'s scroller.
56
+ * @slot app-icon - The mark shown before the page title whenever this slot has content, clipped to the shell's box.
58
57
  * @slot side-nav - The navigation track. Works best with `<nile-side-bar>`.
59
58
  * @slot aside - A secondary column between the content container and the panel.
60
- * @slot panel - The AI panel's body. When nothing is slotted here the panel is
61
- * unmounted and `openPanel()` is a no-op.
59
+ * @slot panel - The AI panel's body. When empty the panel is unmounted and `openPanel()` is a no-op.
62
60
  * @slot panel-header - Replaces the default panel heading (mark plus title).
63
61
  * @slot panel-actions - Controls added before the panel's close button.
64
- * @slot panel-footer - A bar docked below the panel's scroll region, for a
65
- * composer.
62
+ * @slot panel-footer - A bar docked below the panel's scroll region, for a composer.
66
63
  *
67
- * @event nile-nav-toggle - Emitted after the nav's collapsed state or its
68
- * forced-rail lock changes. `detail: { collapsed, locked }`.
69
- * @event nile-panel-toggle - Emitted after the panel opens or closes.
70
- * `detail: { open, mode }`.
71
- * @event nile-panel-mode-change - Emitted when the panel flips between docked
72
- * and overlay. `detail: { mode }`.
64
+ * @event nile-nav-toggle - Emitted after the nav's collapsed state or forced-rail lock changes. `detail: { collapsed, locked }`.
65
+ * @event nile-panel-toggle - Emitted after the panel opens or closes. `detail: { open, mode }`.
66
+ * @event nile-panel-mode-change - Emitted when the panel flips between docked and overlay. `detail: { mode }`.
67
+ * @event nile-click - Emitted when the header's back control is pressed, or `goBackHeader()` is called. `detail: { value: 'back' }`. Emitted on the shell itself, so check `event.target === shell` as well as the value: a `nile-click` from page content bubbles through the shell too.
68
+ * @event nile-panel-back - Emitted when the panel's back control is pressed, whether the shell's built-in panel chrome or a slotted `nile-app-shell-panel` draws it.
73
69
  *
74
70
  * @csspart skip-link - The skip-to-content link.
75
71
  * @csspart banner - The banner region.
@@ -77,12 +73,15 @@ interface ShellGeometry {
77
73
  * @csspart top-bar - The top bar, in either placement.
78
74
  * @csspart nav-toggle - The built-in nav toggle button.
79
75
  * @csspart rule - The vertical rule after the nav toggle.
80
- * @csspart top-bar-actions - The end-aligned top bar action group. Unmounted
81
- * when it holds neither slotted actions nor the Ask button, so it reserves
82
- * no gap.
76
+ * @csspart ask-rule - The vertical rule before the Ask button, drawn when `top-bar-actions` has content.
77
+ * @csspart top-bar-actions - The end-aligned top bar action group.
83
78
  * @csspart ask - The built-in Ask button, the shell's single door to the panel.
84
- * A `<nile-button>`; its inner surface and label are exported as
85
- * `ask__base` and `ask__label`.
79
+ * @csspart title-area - The header's own title block: back control, app icon, title, badge and description.
80
+ * @csspart page-title - The title text, exposed as a heading whose level `headingLevel` sets.
81
+ * @csspart description - The second line under the title.
82
+ * @csspart badge - The pill after the title.
83
+ * @csspart back - The header back control, shown by `headerBack`.
84
+ * @csspart app-icon - The block holding the `app-icon` slot, shown when the slot has content and sized by `--nile-app-shell-app-icon-size`.
86
85
  * @csspart tabs - The hoisted tab-strip region, under the top bar.
87
86
  * @csspart side-nav - The navigation track.
88
87
  * @csspart content-section - The flex row holding container, aside and panel.
@@ -91,42 +90,32 @@ interface ShellGeometry {
91
90
  * @csspart aside - The aside region.
92
91
  * @csspart panel - The AI panel.
93
92
  * @csspart panel-header - The panel's header bar.
93
+ * @csspart panel-back - The panel's back control, shown by `panelBack`.
94
94
  * @csspart panel-title - The panel's title text.
95
95
  * @csspart panel-actions - The panel's header action group.
96
96
  * @csspart panel-close - The panel's close button.
97
97
  * @csspart panel-body - The panel's scroll region.
98
98
  * @csspart panel-footer - The panel's footer.
99
99
  *
100
- * @cssproperty [--nile-app-shell-side-nav-width=260px] - Expanded nav track.
101
- * @cssproperty [--nile-app-shell-side-nav-rail-width=64px] - Collapsed track.
102
- * @cssproperty [--nile-app-shell-side-nav-bg=transparent] - Surface behind a
103
- * slotted side nav. Transparent by default: the nav and the page share one
104
- * surface.
105
- * @cssproperty [--nile-app-shell-side-nav-border=0] - Edge on a slotted side
106
- * nav.
107
- * @cssproperty [--nile-app-shell-gutter=8px] - Content-section padding, and the
108
- * gap between container, aside and panel.
109
- * @cssproperty [--nile-app-shell-content-padding=24px] - Container inner
110
- * padding. The origin every top bar starts at.
111
- * @cssproperty [--nile-app-shell-top-bar-height=56px] - Top bar and panel
112
- * header height. Drops to 48px under 600px of viewport height.
113
- * @cssproperty [--nile-app-shell-tabs-height=48px] - Tab strip height.
114
- * @cssproperty [--nile-app-shell-panel-width=480px] - Docked panel width, and
115
- * the `min()` ceiling for the overlay drawer.
116
- * @cssproperty [--nile-app-shell-panel-min-width=360px] - How far a docked
117
- * panel shrinks to keep pushing rather than covering the page.
100
+ * @cssproperty [--nile-app-shell-side-nav-width=240px] - Expanded nav track.
101
+ * @cssproperty [--nile-app-shell-side-nav-rail-width=70px] - Collapsed track.
102
+ * @cssproperty [--nile-app-shell-side-nav-bg=transparent] - Surface behind a slotted side nav.
103
+ * @cssproperty [--nile-app-shell-side-nav-border=0] - Edge on a slotted side nav.
104
+ * @cssproperty [--nile-app-shell-gutter=4px] - Content-section padding, and the gap between container, aside and panel.
105
+ * @cssproperty [--nile-app-shell-content-padding=24px] - Container inner padding.
106
+ * @cssproperty [--nile-app-shell-top-bar-height=68px] - Top bar height.
107
+ * @cssproperty [--nile-app-shell-panel-header-height=56px] - The panel header's own height.
108
+ * @cssproperty [--nile-app-shell-tabs-height=33px] - Tab strip height.
109
+ * @cssproperty [--nile-app-shell-panel-width=480px] - Docked panel width, and the `min()` ceiling for the overlay drawer.
110
+ * @cssproperty [--nile-app-shell-panel-min-width=360px] - How far a docked panel shrinks to keep pushing rather than covering the page.
118
111
  * @cssproperty [--nile-app-shell-aside-width=320px] - Aside width.
119
- * @cssproperty [--nile-app-shell-aside-min-width=240px] - How far the aside
120
- * shrinks before the page is asked for room.
112
+ * @cssproperty [--nile-app-shell-aside-min-width=240px] - How far the aside shrinks before the page is asked for room.
121
113
  * @cssproperty [--nile-app-shell-content-min-width=640px] - Container floor.
122
- * @cssproperty [--nile-app-shell-min-width=768px] - Below this the window
123
- * scrolls rather than the shell compressing.
124
- * @cssproperty [--nile-app-shell-centered-width=824px] - Column width under
125
- * `content-align="centered"`.
126
- * @cssproperty [--nile-app-shell-utility-size=32px] - Height of the Ask
127
- * button. The toggle and close controls are nile-buttons and size themselves.
128
- * @cssproperty [--nile-app-shell-radius=16px] - Container, aside and panel
129
- * radius.
114
+ * @cssproperty [--nile-app-shell-min-width=768px] - Below this the window scrolls rather than the shell compressing.
115
+ * @cssproperty [--nile-app-shell-centered-width=824px] - Column width under `content-align="centered"`.
116
+ * @cssproperty [--nile-app-shell-app-icon-size=20px] - The app icon's box.
117
+ * @cssproperty [--nile-app-shell-utility-size=32px] - Box the shell's own icon controls sit in.
118
+ * @cssproperty [--nile-app-shell-radius=16px] - Container, aside and panel radius.
130
119
  * @cssproperty [--nile-app-shell-duration=250ms] - Track and panel motion.
131
120
  * @cssproperty [--nile-app-shell-ease] - Easing for moves and resizes.
132
121
  *
@@ -136,6 +125,9 @@ interface ShellGeometry {
136
125
  * @method expandNav() - Expand the side nav.
137
126
  * @method collapseNav() - Collapse the side nav to a rail.
138
127
  * @method toggleNav(force?) - Toggle, or force a specific collapsed state.
128
+ * @method goBackHeader() - Emit `nile-click` with `{ value: 'back' }`, as the header's back control does.
129
+ * @method goBackPanel() - Emit `nile-panel-back`, as the panel's back control does.
130
+ * @method goBack() - Deprecated alias of `goBackPanel()`.
139
131
  */
140
132
  @customElement('nile-app-shell')
141
133
  export class NileAppShell extends NileElement {
@@ -143,10 +135,7 @@ export class NileAppShell extends NileElement {
143
135
  return [styles];
144
136
  }
145
137
 
146
- /**
147
- * The user's nav preference. The nav can still render as a rail while this is
148
- * false — see `navLocked`.
149
- */
138
+ /** The user's nav preference. The nav can still render as a rail while this is false — see `navLocked`. */
150
139
  @property({ type: Boolean, reflect: true, attribute: 'nav-collapsed' })
151
140
  navCollapsed: boolean = false;
152
141
 
@@ -154,10 +143,7 @@ export class NileAppShell extends NileElement {
154
143
  @property({ type: Boolean, reflect: true, attribute: 'panel-open' })
155
144
  panelOpen: boolean = false;
156
145
 
157
- /**
158
- * Unmounts the AI panel for this route even when the `panel` slot is filled.
159
- * `openPanel()` becomes a no-op.
160
- */
146
+ /** Unmounts the AI panel for this route even when the `panel` slot is filled; `openPanel()` becomes a no-op. */
161
147
  @property({ type: Boolean, reflect: true, attribute: 'no-panel' })
162
148
  noPanel: boolean = false;
163
149
 
@@ -165,6 +151,30 @@ export class NileAppShell extends NileElement {
165
151
  @property({ type: String, attribute: 'panel-label' })
166
152
  panelLabel: string = 'Ask Aquera';
167
153
 
154
+ /** The page title, rendered as the top bar's own heading (`role="heading"`, level from `headingLevel`) before anything slotted into `top-bar`; leave unset and no title block renders. */
155
+ @property({ type: String, attribute: true, reflect: true })
156
+ pageTitle?: string;
157
+
158
+ /** The `aria-level` of the `pageTitle` heading, 1–6, so the title can fit the page's own heading outline; e.g. 2 when the page body or banner already has its h1. Out-of-range values clamp to 1–6 and non-numbers fall back to 1. */
159
+ @property({ type: Number, attribute: true, reflect: true })
160
+ headingLevel: number = 1;
161
+
162
+ /** A second line under the page title; a modifier on `pageTitle` that renders nothing with no title, and takes the bar to 76px. */
163
+ @property({ type: String, attribute: true, reflect: true })
164
+ description?: string;
165
+
166
+ /** A pill after the page title; a modifier on `pageTitle` like `description`, so with no title there is no badge. */
167
+ @property({ type: String, attribute: true, reflect: true })
168
+ badge?: string;
169
+
170
+ /** Shows the back arrow before the page title; pressing it emits `nile-click` with `{ value: 'back' }`. Independent of the `app-icon` slot. */
171
+ @property({ type: Boolean, attribute: true, reflect: true })
172
+ headerBack: boolean = false;
173
+
174
+ /** Accessible name of the header back control. Never rendered as visible text. */
175
+ @property({ type: String, attribute: true, reflect: true })
176
+ headerBackLabel: string = 'Back';
177
+
168
178
  /** Hides the built-in nav toggle. Mount `nile-app-shell-nav-toggle` yourself. */
169
179
  @property({ type: Boolean, reflect: true, attribute: 'no-nav-toggle' })
170
180
  noNavToggle: boolean = false;
@@ -189,13 +199,7 @@ export class NileAppShell extends NileElement {
189
199
  @property({ type: String, reflect: true, attribute: 'content-align' })
190
200
  contentAlign: NileAppShellContentAlign = 'fluid';
191
201
 
192
- /**
193
- * Makes the slotted page root a size query container, so page CSS can size
194
- * against the content container instead of the viewport. Query it unnamed —
195
- * `@container (min-width: 720px)` — because a container name declared inside
196
- * the shell's shadow tree is not visible to page CSS. Opt-in, because it
197
- * also applies layout containment to that element.
198
- */
202
+ /** Makes the slotted page root a size query container, so page CSS can size against the content container instead of the viewport. */
199
203
  @property({ type: Boolean, reflect: true, attribute: 'content-container' })
200
204
  contentContainer: boolean = false;
201
205
 
@@ -207,14 +211,7 @@ export class NileAppShell extends NileElement {
207
211
  @property({ type: Boolean, reflect: true, attribute: 'no-padding' })
208
212
  noPadding: boolean = false;
209
213
 
210
- /**
211
- * The label on the built-in Ask button.
212
- *
213
- * The button is the shell's, not the app's: it is the one control that has
214
- * to know whether a panel is mounted for this route and how to open it, and
215
- * slotted content can see neither. Everything else in the bar — the
216
- * breadcrumb, the page actions, the tab strip — is slotted markup.
217
- */
214
+ /** The label on the built-in Ask button. */
218
215
  @property({ type: String, attribute: 'ask-label' })
219
216
  askLabel: string = 'Ask';
220
217
 
@@ -222,36 +219,31 @@ export class NileAppShell extends NileElement {
222
219
  @property({ type: Boolean, reflect: true, attribute: 'no-ask' })
223
220
  noAsk: boolean = false;
224
221
 
222
+ /** Shows a back control at the start of the panel header for a panel sub-view; emits `nile-panel-back`. */
223
+ @property({ type: Boolean, reflect: true })
224
+ panelBack: boolean = false;
225
+
226
+ /** Accessible name of the panel's back control. */
227
+ @property({ type: String })
228
+ panelBackLabel: string = 'Back';
229
+
230
+ /** Hides the tab strip for this route even when the `tabs` slot is filled. */
231
+ @property({ type: Boolean, reflect: true })
232
+ noTabs: boolean = false;
233
+
225
234
  /** Sizes the shell to the viewport instead of its parent. */
226
235
  @property({ type: Boolean, reflect: true, attribute: 'full-height' })
227
236
  fullHeight: boolean = false;
228
237
 
229
- /**
230
- * Below this shell width the panel overlays the content instead of pushing
231
- * it, and the nav is forced to a rail.
232
- *
233
- * Zero — the default — means never: the panel is furniture that squeezes the
234
- * page, at every width. It earns its room from the nav rail first and from
235
- * its own width next (see `--nile-app-shell-panel-min-width`), so it does
236
- * not have to cover the page to fit. Set a width here to opt back into an
237
- * overlay drawer below it.
238
- */
238
+ /** Below this shell width the panel overlays the content instead of pushing it, and the nav is forced to a rail; zero (default) means never. */
239
239
  @property({ type: Number, attribute: 'overlay-breakpoint' })
240
240
  overlayBreakpoint: number = 0;
241
241
 
242
- /**
243
- * Below this shell width an open docked panel forces the nav to a rail.
244
- * A floor, not the whole rule: the nav also rails as soon as the expanded
245
- * track is what stops the row from fitting. This is the first thing the
246
- * panel takes room from, so it can push rather than cover.
247
- */
242
+ /** Below this shell width an open docked panel forces the nav to a rail. */
248
243
  @property({ type: Number, attribute: 'docked-rail-breakpoint' })
249
244
  dockedRailBreakpoint: number = 1440;
250
245
 
251
- /**
252
- * Persists the nav preference and the docked panel state under this key.
253
- * Overlay panel state is never persisted — a reload opens closed.
254
- */
246
+ /** Persists the nav preference and docked panel state under this key. Overlay panel state is never persisted. */
255
247
  @property({ type: String, attribute: 'persist-key' })
256
248
  persistKey?: string;
257
249
 
@@ -266,15 +258,11 @@ export class NileAppShell extends NileElement {
266
258
  @state() private _hasTopBar: boolean = false;
267
259
  @state() private _hasTopBarActions: boolean = false;
268
260
  @state() private _hasTabs: boolean = false;
261
+ @state() private _hasAppIcon: boolean = false;
269
262
  @state() private _hasPanel: boolean = false;
270
263
  @state() private _hasPanelFooter: boolean = false;
271
264
 
272
- /**
273
- * True when a `nile-app-shell-panel` is slotted. That component owns the
274
- * panel's heading, actions, close button and footer, so the shell renders a
275
- * bare region and keeps only the geometry and the open state — the same
276
- * hand-off as `no-nav-toggle` plus `nile-app-shell-nav-toggle`.
277
- */
265
+ /** True when a `nile-app-shell-panel` is slotted, owning the panel's own chrome so the shell renders a bare region. */
278
266
  @state() private _panelOwnsChrome: boolean = false;
279
267
  @state() private _hasAside: boolean = false;
280
268
 
@@ -299,6 +287,11 @@ export class NileAppShell extends NileElement {
299
287
  return this._hasPanel && !this.noPanel;
300
288
  }
301
289
 
290
+ /** Whether the tab strip is rendered: something slotted, and not switched off. */
291
+ public get tabsVisible(): boolean {
292
+ return this._hasTabs && !this.noTabs;
293
+ }
294
+
302
295
  /** Whether the built-in Ask button is rendered. */
303
296
  public get askVisible(): boolean {
304
297
  return this.panelAvailable && !this.noAsk;
@@ -333,8 +326,7 @@ export class NileAppShell extends NileElement {
333
326
  changed.has('noPanel') ||
334
327
  changed.has('overlayBreakpoint') ||
335
328
  changed.has('dockedRailBreakpoint') ||
336
- // Mounting an aside changes what a docked panel has to fit beside.
337
- // Private state is outside `keyof this`, so ask the map directly.
329
+ // Aside/side-nav mounting changes what a docked panel fits beside; ask the map since private state is outside `keyof this`.
338
330
  (changed as Map<string, unknown>).has('_hasAside') ||
339
331
  (changed as Map<string, unknown>).has('_hasSideNav')
340
332
  ) {
@@ -352,11 +344,19 @@ export class NileAppShell extends NileElement {
352
344
  this.toggleAttribute('has-side-nav', this._hasSideNav);
353
345
  this.toggleAttribute(
354
346
  'has-top-bar',
355
- this._hasTopBar || (!this.noNavToggle && this._hasSideNav)
347
+ this._hasTopBar ||
348
+ this._hasTitleBlock ||
349
+ (!this.noNavToggle && this._hasSideNav)
350
+ );
351
+ // Only with a title to sit under: see _hasTitleBlock.
352
+ this.toggleAttribute(
353
+ 'has-description',
354
+ !!this.description && !!this.pageTitle
356
355
  );
357
356
  this.toggleAttribute('has-top-bar-actions', this._hasTopBarActions);
357
+ this.toggleAttribute('has-app-icon', this._hasAppIcon);
358
358
  this.toggleAttribute('has-ask', this.askVisible);
359
- this.toggleAttribute('has-tabs', this._hasTabs);
359
+ this.toggleAttribute('has-tabs', this.tabsVisible);
360
360
  this.toggleAttribute('has-aside', this._hasAside);
361
361
  this.toggleAttribute('has-panel', this.panelAvailable);
362
362
  this.toggleAttribute('has-panel-footer', this._hasPanelFooter);
@@ -380,17 +380,29 @@ export class NileAppShell extends NileElement {
380
380
  this._setPanelOpen(false);
381
381
  }
382
382
 
383
- /**
384
- * Toggle the panel, or force a state. Call `openPanel()` from a page action —
385
- * a page action that toggles closes the panel for a user who already had it
386
- * open.
387
- */
383
+ /** Toggle the panel, or force a state. */
388
384
  public togglePanel(force?: boolean): void {
389
385
  const next = force ?? !this.panelOpen;
390
386
  if (next) this.openPanel();
391
387
  else this.closePanel();
392
388
  }
393
389
 
390
+ /** Emit `nile-click` with `{ value: 'back' }`, as the header's back control does; the page owns the history. */
391
+ public goBackHeader(): void {
392
+ const detail: NileAppShellClickDetail = { value: 'back' };
393
+ this.emit('nile-click', detail);
394
+ }
395
+
396
+ /** Emit `nile-panel-back`, as the panel's back control does; the page owns the view stack. */
397
+ public goBackPanel(): void {
398
+ this.emit('nile-panel-back');
399
+ }
400
+
401
+ /** @deprecated Use `goBackPanel()`; kept as an alias that emits `nile-panel-back`. */
402
+ public goBack(): void {
403
+ this.goBackPanel();
404
+ }
405
+
394
406
  /** Expand the side nav. */
395
407
  public expandNav(): void {
396
408
  this._setNavCollapsed(false);
@@ -443,11 +455,7 @@ export class NileAppShell extends NileElement {
443
455
  }
444
456
  }
445
457
 
446
- /**
447
- * The shell's geometry, read off its own custom properties so a consumer's
448
- * overrides are part of the sum. Falls back to the stylesheet's defaults
449
- * before the element has a computed style.
450
- */
458
+ /** The shell's geometry, read off its own custom properties, falling back to defaults before a computed style exists. */
451
459
  private _geometry(): ShellGeometry {
452
460
  const style = getComputedStyle(this);
453
461
  const px = (name: string, fallback: number): number => {
@@ -455,51 +463,35 @@ export class NileAppShell extends NileElement {
455
463
  return Number.isFinite(value) ? value : fallback;
456
464
  };
457
465
  return {
458
- nav: px('--nile-app-shell-side-nav-width', 260),
459
- rail: px('--nile-app-shell-side-nav-rail-width', 64),
460
- gutter: px('--nile-app-shell-gutter', 8),
466
+ // Read the substituted value so they follow the active theme; the literals are the enterprise fallback.
467
+ nav: px('--nile-app-shell-side-nav-width', 240),
468
+ rail: px('--nile-app-shell-side-nav-rail-width', 70),
469
+ gutter: px('--nile-app-shell-gutter', 4),
461
470
  content: px('--nile-app-shell-content-min-width', 640),
462
471
  aside: px('--nile-app-shell-aside-width', 320),
463
472
  panel: px('--nile-app-shell-panel-width', 480),
464
473
  };
465
474
  }
466
475
 
467
- /**
468
- * The shell width a docked panel needs to sit beside the content floor, over
469
- * a side-nav `track` wide.
470
- *
471
- * A fixed breakpoint cannot answer this: what shares the row changes per
472
- * route. An aside alone adds 328px, so a shell wide enough to hold the
473
- * expanded nav beside a docked panel on one route is 328px short on the
474
- * next. Summing what is actually mounted rails the nav exactly when the
475
- * track is what the row cannot afford.
476
- */
476
+ /** The shell width a docked panel needs to sit beside the content floor, over a side-nav `track` wide. */
477
477
  private _dockRequirement(geo: ShellGeometry, track: number): number {
478
478
  const nav = this._hasSideNav ? track : 0;
479
479
  const aside = this._hasAside ? geo.aside + geo.gutter : 0;
480
- // Two gutters of padding on the content column, one between container and
481
- // panel; the aside carries its own gutter above.
482
- return nav + geo.content + aside + geo.panel + geo.gutter * 3;
480
+ /* The content row's chrome is end-edge padding plus one flex gap between container and panel (an aside carries its own gap above), and start-edge padding too when no nav track is there to sit flush against. */
481
+ const gutters = this._hasSideNav ? 2 : 3;
482
+ return nav + geo.content + aside + geo.panel + geo.gutter * gutters;
483
483
  }
484
484
 
485
485
  private _recompute(): void {
486
486
  const width = this._width;
487
487
  const geo = this._geometry();
488
488
 
489
- /*
490
- * Overlay is opt-in and nothing derives it: a panel that covers the page
491
- * hides the thing the user is asking about. Running out of room is not a
492
- * reason to switch — the rail and the panel's own min width absorb that —
493
- * so the shell only overlays below a width the author names.
494
- */
489
+ /* Overlay is opt-in: the shell only overlays below a width the author names, never from running out of room. */
495
490
  const overlay =
496
491
  this.overlayBreakpoint > 0 && width > 0 && width < this.overlayBreakpoint;
497
492
  const mode: NileAppShellPanelMode = overlay ? 'overlay' : 'docked';
498
493
 
499
- // A docked panel squeezes the container, so a narrow shell trades the
500
- // expanded nav for content width before the page gives any up. Measured
501
- // against the expanded track: the nav rails exactly when it is the track
502
- // that stops the row from fitting.
494
+ // A docked panel squeezes the container, so a narrow shell trades the expanded nav for content width, measured against the expanded track.
503
495
  const railAt = Math.max(
504
496
  this.dockedRailBreakpoint,
505
497
  this._dockRequirement(geo, geo.nav)
@@ -620,6 +612,9 @@ export class NileAppShell extends NileElement {
620
612
  case 'tabs':
621
613
  this._hasTabs = filled;
622
614
  break;
615
+ case 'app-icon':
616
+ this._hasAppIcon = filled;
617
+ break;
623
618
  case 'aside':
624
619
  this._hasAside = filled;
625
620
  break;
@@ -646,7 +641,7 @@ export class NileAppShell extends NileElement {
646
641
  return html`
647
642
  <div class="nav-toggle-group">
648
643
  <nile-button
649
- class="nav-toggle"
644
+ class="nav-toggle utility-control"
650
645
  part="nav-toggle"
651
646
  variant="ghost"
652
647
  circle
@@ -662,12 +657,100 @@ export class NileAppShell extends NileElement {
662
657
  @click=${() => this.toggleNav()}
663
658
  >
664
659
  ${renderGlyph(
665
- expanded ? 'ng-panel-left-close' : 'ng-panel-left-open',
666
- 24,
660
+ expanded ? 'ng-panel-left' : 'ng-panel-right',
661
+ 20,
667
662
  'currentColor'
668
663
  )}
669
664
  </nile-button>
670
- ${this._hasTopBar ? html`<span class="rule" part="rule"></span>` : ''}
665
+ ${this._hasTopBar || this._hasTitleBlock
666
+ ? html`<span class="rule" part="rule"></span>`
667
+ : ''}
668
+ </div>
669
+ `;
670
+ }
671
+
672
+ /** Whether the header has a title block of its own to draw: a title, the back arrow or a slotted app icon mounts it. */
673
+ private get _hasTitleBlock(): boolean {
674
+ return !!this.pageTitle || this.headerBack || this._hasAppIcon;
675
+ }
676
+
677
+ /** `headingLevel` as a safe `aria-level`: a whole number clamped to 1–6, and 1 for anything non-numeric. */
678
+ private get _headingLevel(): number {
679
+ const level = Math.trunc(Number(this.headingLevel));
680
+ return Number.isFinite(level) ? Math.min(6, Math.max(1, level)) : 1;
681
+ }
682
+
683
+ private _renderBack(): TemplateResult | string {
684
+ if (!this.headerBack) return '';
685
+ return html`
686
+ <span class="back-line">
687
+ <button
688
+ class="back"
689
+ part="back"
690
+ type="button"
691
+ aria-label=${this.headerBackLabel}
692
+ title=${this.headerBackLabel}
693
+ @click=${() => this.goBackHeader()}
694
+ >
695
+ ${renderGlyph('ng-arrow-narrow-left', 20, 'currentColor')}
696
+ </button>
697
+ </span>
698
+ `;
699
+ }
700
+
701
+ private _renderAppIconSlot(): TemplateResult {
702
+ return html`<slot
703
+ name="app-icon"
704
+ @slotchange=${(e: Event) => this._onSlotChange('app-icon', e)}
705
+ ></slot>`;
706
+ }
707
+
708
+ /* The app-icon slot has to exist whenever the stack does not, or a slotted icon could never mount it: park it, hidden. */
709
+ private _renderAppIconProbe(): TemplateResult {
710
+ return html`<span class="app-icon-probe" hidden
711
+ >${this._renderAppIconSlot()}</span
712
+ >`;
713
+ }
714
+
715
+ private _renderTitleBlock(): TemplateResult {
716
+ if (!this._hasTitleBlock) return this._renderAppIconProbe();
717
+
718
+ /* The stack is the title and/or the app icon; a back arrow on its own brings no empty stack. */
719
+ if (!this.pageTitle && !this._hasAppIcon) {
720
+ return html`
721
+ <div class="title-area" part="title-area">
722
+ ${this._renderBack()} ${this._renderAppIconProbe()}
723
+ </div>
724
+ `;
725
+ }
726
+
727
+ /* The back arrow sits beside the stack; the app icon starts the stack's first row, so the description lines up under the icon when there is one and under the title when not. */
728
+ return html`
729
+ <div class="title-area" part="title-area">
730
+ ${this._renderBack()}
731
+ <div class="title-stack">
732
+ <div class="title-row">
733
+ <span class="app-icon" part="app-icon"
734
+ >${this._renderAppIconSlot()}</span
735
+ >
736
+ ${this.pageTitle
737
+ ? html`<div
738
+ class="page-title"
739
+ part="page-title"
740
+ role="heading"
741
+ aria-level=${this._headingLevel}
742
+ >${this.pageTitle}</div>`
743
+ : ''}
744
+ ${this.pageTitle && this.badge
745
+ ? html`<span class="badge" part="badge">${this.badge}</span>`
746
+ : ''}
747
+ </div>
748
+ ${this.pageTitle && this.description
749
+ ? html`<p class="description" part="description">
750
+ ${this.description}
751
+ </p>`
752
+ : ''}
753
+ </div>
671
754
  </div>
672
755
  `;
673
756
  }
@@ -682,6 +765,7 @@ export class NileAppShell extends NileElement {
682
765
  >
683
766
  ${this._renderNavToggle()}
684
767
  <div class="top-bar-content">
768
+ ${this._renderTitleBlock()}
685
769
  <slot
686
770
  name="top-bar"
687
771
  @slotchange=${(e: Event) => this._onSlotChange('top-bar', e)}
@@ -693,13 +777,16 @@ export class NileAppShell extends NileElement {
693
777
  @slotchange=${(e: Event) =>
694
778
  this._onSlotChange('top-bar-actions', e)}
695
779
  ></slot>
780
+ ${this.askVisible && this._hasTopBarActions
781
+ ? html`<span class="rule ask-rule" part="ask-rule"></span>`
782
+ : ''}
696
783
  ${this.askVisible
697
784
  ? html`
698
785
  <nile-button
699
786
  class="ask"
700
787
  part="ask"
701
788
  exportparts="base: ask__base, label: ask__label"
702
- variant="primary"
789
+ variant="secondary"
703
790
  @click=${() => this.openPanel()}
704
791
  >
705
792
  <span slot="prefix" class="ask-mark">
@@ -784,6 +871,19 @@ export class NileAppShell extends NileElement {
784
871
  ></slot>`
785
872
  : html`
786
873
  <header class="panel-header" part="panel-header">
874
+ ${this.panelBack
875
+ ? html`<nile-button
876
+ class="panel-back utility-control"
877
+ part="panel-back"
878
+ variant="ghost"
879
+ circle
880
+ title=${this.panelBackLabel}
881
+ aria-label=${this.panelBackLabel}
882
+ @click=${() => this.goBackPanel()}
883
+ >
884
+ ${renderGlyph('ng-arrow-left', 20, 'currentColor')}
885
+ </nile-button>`
886
+ : ''}
787
887
  <slot name="panel-header">
788
888
  <span class="panel-heading">
789
889
  <span class="panel-mark">
@@ -797,7 +897,7 @@ export class NileAppShell extends NileElement {
797
897
  <div class="panel-actions" part="panel-actions">
798
898
  <slot name="panel-actions"></slot>
799
899
  <nile-button
800
- class="panel-close"
900
+ class="panel-close utility-control"
801
901
  part="panel-close"
802
902
  variant="ghost"
803
903
  circle
@@ -805,7 +905,7 @@ export class NileAppShell extends NileElement {
805
905
  aria-label="Close ${this.panelLabel}"
806
906
  @click=${() => this.closePanel()}
807
907
  >
808
- ${renderGlyph('ng-x-close', 24, 'currentColor')}
908
+ ${renderGlyph('ng-x-close', 20, 'currentColor')}
809
909
  </nile-button>
810
910
  </div>
811
911
  </header>