@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
@@ -13,9 +13,7 @@ import { renderGlyph } from '../internal/glyph';
13
13
  import NileElement from '../internal/nile-element';
14
14
  import '../nile-button/nile-button';
15
15
  /**
16
- * @summary The application frame every route inherits: a side nav, a content
17
- * container, an optional aside and an AI assistant panel. It holds anything —
18
- * the shell owns the boxes, the page owns what goes in them.
16
+ * @summary The application frame every route inherits: a side nav, a content container, an optional aside and an AI assistant panel.
19
17
  *
20
18
  * @status stable
21
19
  * @since 2.0
@@ -27,29 +25,22 @@ import '../nile-button/nile-button';
27
25
  *
28
26
  * @slot - The page body, inside the content container.
29
27
  * @slot banner - A full-width strip above every other region.
30
- * @slot top-bar - The page-title bar. Mounted inside the container by default,
31
- * or spanning the shell when `top-bar-placement="shell"`. The nav toggle is
32
- * rendered before whatever is slotted here.
28
+ * @slot top-bar - The page-title bar, mounted inside the container by default or spanning the shell when `top-bar-placement="shell"`.
33
29
  * @slot top-bar-actions - Controls pinned to the end of the top bar.
34
- * @slot tabs - A hoisted second-level tab strip, flush under the top bar and
35
- * outside `<main>`'s scroller, so it never scrolls away. The region carries
36
- * the strip's height, padding and rule, so slot a `<nile-tab-group>` or the
37
- * tab controls themselves.
30
+ * @slot tabs - A hoisted second-level tab strip, flush under the top bar and outside `<main>`'s scroller.
31
+ * @slot app-icon - The mark shown before the page title whenever this slot has content, clipped to the shell's box.
38
32
  * @slot side-nav - The navigation track. Works best with `<nile-side-bar>`.
39
33
  * @slot aside - A secondary column between the content container and the panel.
40
- * @slot panel - The AI panel's body. When nothing is slotted here the panel is
41
- * unmounted and `openPanel()` is a no-op.
34
+ * @slot panel - The AI panel's body. When empty the panel is unmounted and `openPanel()` is a no-op.
42
35
  * @slot panel-header - Replaces the default panel heading (mark plus title).
43
36
  * @slot panel-actions - Controls added before the panel's close button.
44
- * @slot panel-footer - A bar docked below the panel's scroll region, for a
45
- * composer.
37
+ * @slot panel-footer - A bar docked below the panel's scroll region, for a composer.
46
38
  *
47
- * @event nile-nav-toggle - Emitted after the nav's collapsed state or its
48
- * forced-rail lock changes. `detail: { collapsed, locked }`.
49
- * @event nile-panel-toggle - Emitted after the panel opens or closes.
50
- * `detail: { open, mode }`.
51
- * @event nile-panel-mode-change - Emitted when the panel flips between docked
52
- * and overlay. `detail: { mode }`.
39
+ * @event nile-nav-toggle - Emitted after the nav's collapsed state or forced-rail lock changes. `detail: { collapsed, locked }`.
40
+ * @event nile-panel-toggle - Emitted after the panel opens or closes. `detail: { open, mode }`.
41
+ * @event nile-panel-mode-change - Emitted when the panel flips between docked and overlay. `detail: { mode }`.
42
+ * @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.
43
+ * @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.
53
44
  *
54
45
  * @csspart skip-link - The skip-to-content link.
55
46
  * @csspart banner - The banner region.
@@ -57,12 +48,15 @@ import '../nile-button/nile-button';
57
48
  * @csspart top-bar - The top bar, in either placement.
58
49
  * @csspart nav-toggle - The built-in nav toggle button.
59
50
  * @csspart rule - The vertical rule after the nav toggle.
60
- * @csspart top-bar-actions - The end-aligned top bar action group. Unmounted
61
- * when it holds neither slotted actions nor the Ask button, so it reserves
62
- * no gap.
51
+ * @csspart ask-rule - The vertical rule before the Ask button, drawn when `top-bar-actions` has content.
52
+ * @csspart top-bar-actions - The end-aligned top bar action group.
63
53
  * @csspart ask - The built-in Ask button, the shell's single door to the panel.
64
- * A `<nile-button>`; its inner surface and label are exported as
65
- * `ask__base` and `ask__label`.
54
+ * @csspart title-area - The header's own title block: back control, app icon, title, badge and description.
55
+ * @csspart page-title - The title text, exposed as a heading whose level `headingLevel` sets.
56
+ * @csspart description - The second line under the title.
57
+ * @csspart badge - The pill after the title.
58
+ * @csspart back - The header back control, shown by `headerBack`.
59
+ * @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`.
66
60
  * @csspart tabs - The hoisted tab-strip region, under the top bar.
67
61
  * @csspart side-nav - The navigation track.
68
62
  * @csspart content-section - The flex row holding container, aside and panel.
@@ -71,42 +65,32 @@ import '../nile-button/nile-button';
71
65
  * @csspart aside - The aside region.
72
66
  * @csspart panel - The AI panel.
73
67
  * @csspart panel-header - The panel's header bar.
68
+ * @csspart panel-back - The panel's back control, shown by `panelBack`.
74
69
  * @csspart panel-title - The panel's title text.
75
70
  * @csspart panel-actions - The panel's header action group.
76
71
  * @csspart panel-close - The panel's close button.
77
72
  * @csspart panel-body - The panel's scroll region.
78
73
  * @csspart panel-footer - The panel's footer.
79
74
  *
80
- * @cssproperty [--nile-app-shell-side-nav-width=260px] - Expanded nav track.
81
- * @cssproperty [--nile-app-shell-side-nav-rail-width=64px] - Collapsed track.
82
- * @cssproperty [--nile-app-shell-side-nav-bg=transparent] - Surface behind a
83
- * slotted side nav. Transparent by default: the nav and the page share one
84
- * surface.
85
- * @cssproperty [--nile-app-shell-side-nav-border=0] - Edge on a slotted side
86
- * nav.
87
- * @cssproperty [--nile-app-shell-gutter=8px] - Content-section padding, and the
88
- * gap between container, aside and panel.
89
- * @cssproperty [--nile-app-shell-content-padding=24px] - Container inner
90
- * padding. The origin every top bar starts at.
91
- * @cssproperty [--nile-app-shell-top-bar-height=56px] - Top bar and panel
92
- * header height. Drops to 48px under 600px of viewport height.
93
- * @cssproperty [--nile-app-shell-tabs-height=48px] - Tab strip height.
94
- * @cssproperty [--nile-app-shell-panel-width=480px] - Docked panel width, and
95
- * the `min()` ceiling for the overlay drawer.
96
- * @cssproperty [--nile-app-shell-panel-min-width=360px] - How far a docked
97
- * panel shrinks to keep pushing rather than covering the page.
75
+ * @cssproperty [--nile-app-shell-side-nav-width=240px] - Expanded nav track.
76
+ * @cssproperty [--nile-app-shell-side-nav-rail-width=70px] - Collapsed track.
77
+ * @cssproperty [--nile-app-shell-side-nav-bg=transparent] - Surface behind a slotted side nav.
78
+ * @cssproperty [--nile-app-shell-side-nav-border=0] - Edge on a slotted side nav.
79
+ * @cssproperty [--nile-app-shell-gutter=4px] - Content-section padding, and the gap between container, aside and panel.
80
+ * @cssproperty [--nile-app-shell-content-padding=24px] - Container inner padding.
81
+ * @cssproperty [--nile-app-shell-top-bar-height=68px] - Top bar height.
82
+ * @cssproperty [--nile-app-shell-panel-header-height=56px] - The panel header's own height.
83
+ * @cssproperty [--nile-app-shell-tabs-height=33px] - Tab strip height.
84
+ * @cssproperty [--nile-app-shell-panel-width=480px] - Docked panel width, and the `min()` ceiling for the overlay drawer.
85
+ * @cssproperty [--nile-app-shell-panel-min-width=360px] - How far a docked panel shrinks to keep pushing rather than covering the page.
98
86
  * @cssproperty [--nile-app-shell-aside-width=320px] - Aside width.
99
- * @cssproperty [--nile-app-shell-aside-min-width=240px] - How far the aside
100
- * shrinks before the page is asked for room.
87
+ * @cssproperty [--nile-app-shell-aside-min-width=240px] - How far the aside shrinks before the page is asked for room.
101
88
  * @cssproperty [--nile-app-shell-content-min-width=640px] - Container floor.
102
- * @cssproperty [--nile-app-shell-min-width=768px] - Below this the window
103
- * scrolls rather than the shell compressing.
104
- * @cssproperty [--nile-app-shell-centered-width=824px] - Column width under
105
- * `content-align="centered"`.
106
- * @cssproperty [--nile-app-shell-utility-size=32px] - Height of the Ask
107
- * button. The toggle and close controls are nile-buttons and size themselves.
108
- * @cssproperty [--nile-app-shell-radius=16px] - Container, aside and panel
109
- * radius.
89
+ * @cssproperty [--nile-app-shell-min-width=768px] - Below this the window scrolls rather than the shell compressing.
90
+ * @cssproperty [--nile-app-shell-centered-width=824px] - Column width under `content-align="centered"`.
91
+ * @cssproperty [--nile-app-shell-app-icon-size=20px] - The app icon's box.
92
+ * @cssproperty [--nile-app-shell-utility-size=32px] - Box the shell's own icon controls sit in.
93
+ * @cssproperty [--nile-app-shell-radius=16px] - Container, aside and panel radius.
110
94
  * @cssproperty [--nile-app-shell-duration=250ms] - Track and panel motion.
111
95
  * @cssproperty [--nile-app-shell-ease] - Easing for moves and resizes.
112
96
  *
@@ -116,24 +100,27 @@ import '../nile-button/nile-button';
116
100
  * @method expandNav() - Expand the side nav.
117
101
  * @method collapseNav() - Collapse the side nav to a rail.
118
102
  * @method toggleNav(force?) - Toggle, or force a specific collapsed state.
103
+ * @method goBackHeader() - Emit `nile-click` with `{ value: 'back' }`, as the header's back control does.
104
+ * @method goBackPanel() - Emit `nile-panel-back`, as the panel's back control does.
105
+ * @method goBack() - Deprecated alias of `goBackPanel()`.
119
106
  */
120
107
  let NileAppShell = class NileAppShell extends NileElement {
121
108
  constructor() {
122
109
  super(...arguments);
123
- /**
124
- * The user's nav preference. The nav can still render as a rail while this is
125
- * false — see `navLocked`.
126
- */
110
+ /** The user's nav preference. The nav can still render as a rail while this is false — see `navLocked`. */
127
111
  this.navCollapsed = false;
128
112
  /** Whether the AI panel is open. */
129
113
  this.panelOpen = false;
130
- /**
131
- * Unmounts the AI panel for this route even when the `panel` slot is filled.
132
- * `openPanel()` becomes a no-op.
133
- */
114
+ /** Unmounts the AI panel for this route even when the `panel` slot is filled; `openPanel()` becomes a no-op. */
134
115
  this.noPanel = false;
135
116
  /** The default panel heading. Ignored when `panel-header` is slotted. */
136
117
  this.panelLabel = 'Ask Aquera';
118
+ /** 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. */
119
+ this.headingLevel = 1;
120
+ /** Shows the back arrow before the page title; pressing it emits `nile-click` with `{ value: 'back' }`. Independent of the `app-icon` slot. */
121
+ this.headerBack = false;
122
+ /** Accessible name of the header back control. Never rendered as visible text. */
123
+ this.headerBackLabel = 'Back';
137
124
  /** Hides the built-in nav toggle. Mount `nile-app-shell-nav-toggle` yourself. */
138
125
  this.noNavToggle = false;
139
126
  /** Drops the Cmd/Ctrl+J and Escape panel shortcuts. */
@@ -146,48 +133,27 @@ let NileAppShell = class NileAppShell extends NileElement {
146
133
  this.topBarPlacement = 'content';
147
134
  /** `centered` caps the page body at `--nile-app-shell-centered-width`. */
148
135
  this.contentAlign = 'fluid';
149
- /**
150
- * Makes the slotted page root a size query container, so page CSS can size
151
- * against the content container instead of the viewport. Query it unnamed —
152
- * `@container (min-width: 720px)` — because a container name declared inside
153
- * the shell's shadow tree is not visible to page CSS. Opt-in, because it
154
- * also applies layout containment to that element.
155
- */
136
+ /** Makes the slotted page root a size query container, so page CSS can size against the content container instead of the viewport. */
156
137
  this.contentContainer = false;
157
138
  /** Drops the container's card chrome for a full-bleed page. */
158
139
  this.flat = false;
159
140
  /** Removes the container's inner padding. */
160
141
  this.noPadding = false;
161
- /**
162
- * The label on the built-in Ask button.
163
- *
164
- * The button is the shell's, not the app's: it is the one control that has
165
- * to know whether a panel is mounted for this route and how to open it, and
166
- * slotted content can see neither. Everything else in the bar — the
167
- * breadcrumb, the page actions, the tab strip — is slotted markup.
168
- */
142
+ /** The label on the built-in Ask button. */
169
143
  this.askLabel = 'Ask';
170
144
  /** Drops the built-in Ask button, for a bar that renders its own. */
171
145
  this.noAsk = false;
146
+ /** Shows a back control at the start of the panel header for a panel sub-view; emits `nile-panel-back`. */
147
+ this.panelBack = false;
148
+ /** Accessible name of the panel's back control. */
149
+ this.panelBackLabel = 'Back';
150
+ /** Hides the tab strip for this route even when the `tabs` slot is filled. */
151
+ this.noTabs = false;
172
152
  /** Sizes the shell to the viewport instead of its parent. */
173
153
  this.fullHeight = false;
174
- /**
175
- * Below this shell width the panel overlays the content instead of pushing
176
- * it, and the nav is forced to a rail.
177
- *
178
- * Zero — the default — means never: the panel is furniture that squeezes the
179
- * page, at every width. It earns its room from the nav rail first and from
180
- * its own width next (see `--nile-app-shell-panel-min-width`), so it does
181
- * not have to cover the page to fit. Set a width here to opt back into an
182
- * overlay drawer below it.
183
- */
154
+ /** 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. */
184
155
  this.overlayBreakpoint = 0;
185
- /**
186
- * Below this shell width an open docked panel forces the nav to a rail.
187
- * A floor, not the whole rule: the nav also rails as soon as the expanded
188
- * track is what stops the row from fitting. This is the first thing the
189
- * panel takes room from, so it can push rather than cover.
190
- */
156
+ /** Below this shell width an open docked panel forces the nav to a rail. */
191
157
  this.dockedRailBreakpoint = 1440;
192
158
  /** The panel's current mode. Derived from the shell's width. */
193
159
  this._panelMode = 'docked';
@@ -198,14 +164,10 @@ let NileAppShell = class NileAppShell extends NileElement {
198
164
  this._hasTopBar = false;
199
165
  this._hasTopBarActions = false;
200
166
  this._hasTabs = false;
167
+ this._hasAppIcon = false;
201
168
  this._hasPanel = false;
202
169
  this._hasPanelFooter = false;
203
- /**
204
- * True when a `nile-app-shell-panel` is slotted. That component owns the
205
- * panel's heading, actions, close button and footer, so the shell renders a
206
- * bare region and keeps only the geometry and the open state — the same
207
- * hand-off as `no-nav-toggle` plus `nile-app-shell-nav-toggle`.
208
- */
170
+ /** True when a `nile-app-shell-panel` is slotted, owning the panel's own chrome so the shell renders a bare region. */
209
171
  this._panelOwnsChrome = false;
210
172
  this._hasAside = false;
211
173
  this._width = 0;
@@ -247,6 +209,10 @@ let NileAppShell = class NileAppShell extends NileElement {
247
209
  get panelAvailable() {
248
210
  return this._hasPanel && !this.noPanel;
249
211
  }
212
+ /** Whether the tab strip is rendered: something slotted, and not switched off. */
213
+ get tabsVisible() {
214
+ return this._hasTabs && !this.noTabs;
215
+ }
250
216
  /** Whether the built-in Ask button is rendered. */
251
217
  get askVisible() {
252
218
  return this.panelAvailable && !this.noAsk;
@@ -276,8 +242,7 @@ let NileAppShell = class NileAppShell extends NileElement {
276
242
  changed.has('noPanel') ||
277
243
  changed.has('overlayBreakpoint') ||
278
244
  changed.has('dockedRailBreakpoint') ||
279
- // Mounting an aside changes what a docked panel has to fit beside.
280
- // Private state is outside `keyof this`, so ask the map directly.
245
+ // Aside/side-nav mounting changes what a docked panel fits beside; ask the map since private state is outside `keyof this`.
281
246
  changed.has('_hasAside') ||
282
247
  changed.has('_hasSideNav')) {
283
248
  this._recompute();
@@ -290,10 +255,15 @@ let NileAppShell = class NileAppShell extends NileElement {
290
255
  this.toggleAttribute('nav-locked', this._navLocked);
291
256
  this.toggleAttribute('has-banner', this._hasBanner);
292
257
  this.toggleAttribute('has-side-nav', this._hasSideNav);
293
- this.toggleAttribute('has-top-bar', this._hasTopBar || (!this.noNavToggle && this._hasSideNav));
258
+ this.toggleAttribute('has-top-bar', this._hasTopBar ||
259
+ this._hasTitleBlock ||
260
+ (!this.noNavToggle && this._hasSideNav));
261
+ // Only with a title to sit under: see _hasTitleBlock.
262
+ this.toggleAttribute('has-description', !!this.description && !!this.pageTitle);
294
263
  this.toggleAttribute('has-top-bar-actions', this._hasTopBarActions);
264
+ this.toggleAttribute('has-app-icon', this._hasAppIcon);
295
265
  this.toggleAttribute('has-ask', this.askVisible);
296
- this.toggleAttribute('has-tabs', this._hasTabs);
266
+ this.toggleAttribute('has-tabs', this.tabsVisible);
297
267
  this.toggleAttribute('has-aside', this._hasAside);
298
268
  this.toggleAttribute('has-panel', this.panelAvailable);
299
269
  this.toggleAttribute('has-panel-footer', this._hasPanelFooter);
@@ -311,11 +281,7 @@ let NileAppShell = class NileAppShell extends NileElement {
311
281
  closePanel() {
312
282
  this._setPanelOpen(false);
313
283
  }
314
- /**
315
- * Toggle the panel, or force a state. Call `openPanel()` from a page action —
316
- * a page action that toggles closes the panel for a user who already had it
317
- * open.
318
- */
284
+ /** Toggle the panel, or force a state. */
319
285
  togglePanel(force) {
320
286
  const next = force ?? !this.panelOpen;
321
287
  if (next)
@@ -323,6 +289,19 @@ let NileAppShell = class NileAppShell extends NileElement {
323
289
  else
324
290
  this.closePanel();
325
291
  }
292
+ /** Emit `nile-click` with `{ value: 'back' }`, as the header's back control does; the page owns the history. */
293
+ goBackHeader() {
294
+ const detail = { value: 'back' };
295
+ this.emit('nile-click', detail);
296
+ }
297
+ /** Emit `nile-panel-back`, as the panel's back control does; the page owns the view stack. */
298
+ goBackPanel() {
299
+ this.emit('nile-panel-back');
300
+ }
301
+ /** @deprecated Use `goBackPanel()`; kept as an alias that emits `nile-panel-back`. */
302
+ goBack() {
303
+ this.goBackPanel();
304
+ }
326
305
  /** Expand the side nav. */
327
306
  expandNav() {
328
307
  this._setNavCollapsed(false);
@@ -371,11 +350,7 @@ let NileAppShell = class NileAppShell extends NileElement {
371
350
  this.openPanel();
372
351
  }
373
352
  }
374
- /**
375
- * The shell's geometry, read off its own custom properties so a consumer's
376
- * overrides are part of the sum. Falls back to the stylesheet's defaults
377
- * before the element has a computed style.
378
- */
353
+ /** The shell's geometry, read off its own custom properties, falling back to defaults before a computed style exists. */
379
354
  _geometry() {
380
355
  const style = getComputedStyle(this);
381
356
  const px = (name, fallback) => {
@@ -383,46 +358,30 @@ let NileAppShell = class NileAppShell extends NileElement {
383
358
  return Number.isFinite(value) ? value : fallback;
384
359
  };
385
360
  return {
386
- nav: px('--nile-app-shell-side-nav-width', 260),
387
- rail: px('--nile-app-shell-side-nav-rail-width', 64),
388
- gutter: px('--nile-app-shell-gutter', 8),
361
+ // Read the substituted value so they follow the active theme; the literals are the enterprise fallback.
362
+ nav: px('--nile-app-shell-side-nav-width', 240),
363
+ rail: px('--nile-app-shell-side-nav-rail-width', 70),
364
+ gutter: px('--nile-app-shell-gutter', 4),
389
365
  content: px('--nile-app-shell-content-min-width', 640),
390
366
  aside: px('--nile-app-shell-aside-width', 320),
391
367
  panel: px('--nile-app-shell-panel-width', 480),
392
368
  };
393
369
  }
394
- /**
395
- * The shell width a docked panel needs to sit beside the content floor, over
396
- * a side-nav `track` wide.
397
- *
398
- * A fixed breakpoint cannot answer this: what shares the row changes per
399
- * route. An aside alone adds 328px, so a shell wide enough to hold the
400
- * expanded nav beside a docked panel on one route is 328px short on the
401
- * next. Summing what is actually mounted rails the nav exactly when the
402
- * track is what the row cannot afford.
403
- */
370
+ /** The shell width a docked panel needs to sit beside the content floor, over a side-nav `track` wide. */
404
371
  _dockRequirement(geo, track) {
405
372
  const nav = this._hasSideNav ? track : 0;
406
373
  const aside = this._hasAside ? geo.aside + geo.gutter : 0;
407
- // Two gutters of padding on the content column, one between container and
408
- // panel; the aside carries its own gutter above.
409
- return nav + geo.content + aside + geo.panel + geo.gutter * 3;
374
+ /* 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. */
375
+ const gutters = this._hasSideNav ? 2 : 3;
376
+ return nav + geo.content + aside + geo.panel + geo.gutter * gutters;
410
377
  }
411
378
  _recompute() {
412
379
  const width = this._width;
413
380
  const geo = this._geometry();
414
- /*
415
- * Overlay is opt-in and nothing derives it: a panel that covers the page
416
- * hides the thing the user is asking about. Running out of room is not a
417
- * reason to switch — the rail and the panel's own min width absorb that —
418
- * so the shell only overlays below a width the author names.
419
- */
381
+ /* Overlay is opt-in: the shell only overlays below a width the author names, never from running out of room. */
420
382
  const overlay = this.overlayBreakpoint > 0 && width > 0 && width < this.overlayBreakpoint;
421
383
  const mode = overlay ? 'overlay' : 'docked';
422
- // A docked panel squeezes the container, so a narrow shell trades the
423
- // expanded nav for content width before the page gives any up. Measured
424
- // against the expanded track: the nav rails exactly when it is the track
425
- // that stops the row from fitting.
384
+ // A docked panel squeezes the container, so a narrow shell trades the expanded nav for content width, measured against the expanded track.
426
385
  const railAt = Math.max(this.dockedRailBreakpoint, this._dockRequirement(geo, geo.nav));
427
386
  const squeezed = !overlay &&
428
387
  this.panelOpen &&
@@ -503,6 +462,9 @@ let NileAppShell = class NileAppShell extends NileElement {
503
462
  case 'tabs':
504
463
  this._hasTabs = filled;
505
464
  break;
465
+ case 'app-icon':
466
+ this._hasAppIcon = filled;
467
+ break;
506
468
  case 'aside':
507
469
  this._hasAside = filled;
508
470
  break;
@@ -526,7 +488,7 @@ let NileAppShell = class NileAppShell extends NileElement {
526
488
  return html `
527
489
  <div class="nav-toggle-group">
528
490
  <nile-button
529
- class="nav-toggle"
491
+ class="nav-toggle utility-control"
530
492
  part="nav-toggle"
531
493
  variant="ghost"
532
494
  circle
@@ -541,9 +503,91 @@ let NileAppShell = class NileAppShell extends NileElement {
541
503
  aria-label=${expanded ? 'Collapse navigation' : 'Expand navigation'}
542
504
  @click=${() => this.toggleNav()}
543
505
  >
544
- ${renderGlyph(expanded ? 'ng-panel-left-close' : 'ng-panel-left-open', 24, 'currentColor')}
506
+ ${renderGlyph(expanded ? 'ng-panel-left' : 'ng-panel-right', 20, 'currentColor')}
545
507
  </nile-button>
546
- ${this._hasTopBar ? html `<span class="rule" part="rule"></span>` : ''}
508
+ ${this._hasTopBar || this._hasTitleBlock
509
+ ? html `<span class="rule" part="rule"></span>`
510
+ : ''}
511
+ </div>
512
+ `;
513
+ }
514
+ /** Whether the header has a title block of its own to draw: a title, the back arrow or a slotted app icon mounts it. */
515
+ get _hasTitleBlock() {
516
+ return !!this.pageTitle || this.headerBack || this._hasAppIcon;
517
+ }
518
+ /** `headingLevel` as a safe `aria-level`: a whole number clamped to 1–6, and 1 for anything non-numeric. */
519
+ get _headingLevel() {
520
+ const level = Math.trunc(Number(this.headingLevel));
521
+ return Number.isFinite(level) ? Math.min(6, Math.max(1, level)) : 1;
522
+ }
523
+ _renderBack() {
524
+ if (!this.headerBack)
525
+ return '';
526
+ return html `
527
+ <span class="back-line">
528
+ <button
529
+ class="back"
530
+ part="back"
531
+ type="button"
532
+ aria-label=${this.headerBackLabel}
533
+ title=${this.headerBackLabel}
534
+ @click=${() => this.goBackHeader()}
535
+ >
536
+ ${renderGlyph('ng-arrow-narrow-left', 20, 'currentColor')}
537
+ </button>
538
+ </span>
539
+ `;
540
+ }
541
+ _renderAppIconSlot() {
542
+ return html `<slot
543
+ name="app-icon"
544
+ @slotchange=${(e) => this._onSlotChange('app-icon', e)}
545
+ ></slot>`;
546
+ }
547
+ /* The app-icon slot has to exist whenever the stack does not, or a slotted icon could never mount it: park it, hidden. */
548
+ _renderAppIconProbe() {
549
+ return html `<span class="app-icon-probe" hidden
550
+ >${this._renderAppIconSlot()}</span
551
+ >`;
552
+ }
553
+ _renderTitleBlock() {
554
+ if (!this._hasTitleBlock)
555
+ return this._renderAppIconProbe();
556
+ /* The stack is the title and/or the app icon; a back arrow on its own brings no empty stack. */
557
+ if (!this.pageTitle && !this._hasAppIcon) {
558
+ return html `
559
+ <div class="title-area" part="title-area">
560
+ ${this._renderBack()} ${this._renderAppIconProbe()}
561
+ </div>
562
+ `;
563
+ }
564
+ /* 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. */
565
+ return html `
566
+ <div class="title-area" part="title-area">
567
+ ${this._renderBack()}
568
+ <div class="title-stack">
569
+ <div class="title-row">
570
+ <span class="app-icon" part="app-icon"
571
+ >${this._renderAppIconSlot()}</span
572
+ >
573
+ ${this.pageTitle
574
+ ? html `<div
575
+ class="page-title"
576
+ part="page-title"
577
+ role="heading"
578
+ aria-level=${this._headingLevel}
579
+ >${this.pageTitle}</div>`
580
+ : ''}
581
+ ${this.pageTitle && this.badge
582
+ ? html `<span class="badge" part="badge">${this.badge}</span>`
583
+ : ''}
584
+ </div>
585
+ ${this.pageTitle && this.description
586
+ ? html `<p class="description" part="description">
587
+ ${this.description}
588
+ </p>`
589
+ : ''}
590
+ </div>
547
591
  </div>
548
592
  `;
549
593
  }
@@ -555,6 +599,7 @@ let NileAppShell = class NileAppShell extends NileElement {
555
599
  >
556
600
  ${this._renderNavToggle()}
557
601
  <div class="top-bar-content">
602
+ ${this._renderTitleBlock()}
558
603
  <slot
559
604
  name="top-bar"
560
605
  @slotchange=${(e) => this._onSlotChange('top-bar', e)}
@@ -565,13 +610,16 @@ let NileAppShell = class NileAppShell extends NileElement {
565
610
  name="top-bar-actions"
566
611
  @slotchange=${(e) => this._onSlotChange('top-bar-actions', e)}
567
612
  ></slot>
613
+ ${this.askVisible && this._hasTopBarActions
614
+ ? html `<span class="rule ask-rule" part="ask-rule"></span>`
615
+ : ''}
568
616
  ${this.askVisible
569
617
  ? html `
570
618
  <nile-button
571
619
  class="ask"
572
620
  part="ask"
573
621
  exportparts="base: ask__base, label: ask__label"
574
- variant="primary"
622
+ variant="secondary"
575
623
  @click=${() => this.openPanel()}
576
624
  >
577
625
  <span slot="prefix" class="ask-mark">
@@ -652,6 +700,19 @@ let NileAppShell = class NileAppShell extends NileElement {
652
700
  ></slot>`
653
701
  : html `
654
702
  <header class="panel-header" part="panel-header">
703
+ ${this.panelBack
704
+ ? html `<nile-button
705
+ class="panel-back utility-control"
706
+ part="panel-back"
707
+ variant="ghost"
708
+ circle
709
+ title=${this.panelBackLabel}
710
+ aria-label=${this.panelBackLabel}
711
+ @click=${() => this.goBackPanel()}
712
+ >
713
+ ${renderGlyph('ng-arrow-left', 20, 'currentColor')}
714
+ </nile-button>`
715
+ : ''}
655
716
  <slot name="panel-header">
656
717
  <span class="panel-heading">
657
718
  <span class="panel-mark">
@@ -665,7 +726,7 @@ let NileAppShell = class NileAppShell extends NileElement {
665
726
  <div class="panel-actions" part="panel-actions">
666
727
  <slot name="panel-actions"></slot>
667
728
  <nile-button
668
- class="panel-close"
729
+ class="panel-close utility-control"
669
730
  part="panel-close"
670
731
  variant="ghost"
671
732
  circle
@@ -673,7 +734,7 @@ let NileAppShell = class NileAppShell extends NileElement {
673
734
  aria-label="Close ${this.panelLabel}"
674
735
  @click=${() => this.closePanel()}
675
736
  >
676
- ${renderGlyph('ng-x-close', 24, 'currentColor')}
737
+ ${renderGlyph('ng-x-close', 20, 'currentColor')}
677
738
  </nile-button>
678
739
  </div>
679
740
  </header>
@@ -707,6 +768,24 @@ __decorate([
707
768
  __decorate([
708
769
  property({ type: String, attribute: 'panel-label' })
709
770
  ], NileAppShell.prototype, "panelLabel", void 0);
771
+ __decorate([
772
+ property({ type: String, attribute: true, reflect: true })
773
+ ], NileAppShell.prototype, "pageTitle", void 0);
774
+ __decorate([
775
+ property({ type: Number, attribute: true, reflect: true })
776
+ ], NileAppShell.prototype, "headingLevel", void 0);
777
+ __decorate([
778
+ property({ type: String, attribute: true, reflect: true })
779
+ ], NileAppShell.prototype, "description", void 0);
780
+ __decorate([
781
+ property({ type: String, attribute: true, reflect: true })
782
+ ], NileAppShell.prototype, "badge", void 0);
783
+ __decorate([
784
+ property({ type: Boolean, attribute: true, reflect: true })
785
+ ], NileAppShell.prototype, "headerBack", void 0);
786
+ __decorate([
787
+ property({ type: String, attribute: true, reflect: true })
788
+ ], NileAppShell.prototype, "headerBackLabel", void 0);
710
789
  __decorate([
711
790
  property({ type: Boolean, reflect: true, attribute: 'no-nav-toggle' })
712
791
  ], NileAppShell.prototype, "noNavToggle", void 0);
@@ -740,6 +819,15 @@ __decorate([
740
819
  __decorate([
741
820
  property({ type: Boolean, reflect: true, attribute: 'no-ask' })
742
821
  ], NileAppShell.prototype, "noAsk", void 0);
822
+ __decorate([
823
+ property({ type: Boolean, reflect: true })
824
+ ], NileAppShell.prototype, "panelBack", void 0);
825
+ __decorate([
826
+ property({ type: String })
827
+ ], NileAppShell.prototype, "panelBackLabel", void 0);
828
+ __decorate([
829
+ property({ type: Boolean, reflect: true })
830
+ ], NileAppShell.prototype, "noTabs", void 0);
743
831
  __decorate([
744
832
  property({ type: Boolean, reflect: true, attribute: 'full-height' })
745
833
  ], NileAppShell.prototype, "fullHeight", void 0);
@@ -773,6 +861,9 @@ __decorate([
773
861
  __decorate([
774
862
  state()
775
863
  ], NileAppShell.prototype, "_hasTabs", void 0);
864
+ __decorate([
865
+ state()
866
+ ], NileAppShell.prototype, "_hasAppIcon", void 0);
776
867
  __decorate([
777
868
  state()
778
869
  ], NileAppShell.prototype, "_hasPanel", void 0);