@frame-kit/ui-ng 0.0.1

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 (220) hide show
  1. package/COMPONENTS.md +683 -0
  2. package/DEVELOPMENT_GUIDE.md +1102 -0
  3. package/LICENSE +21 -0
  4. package/README.md +69 -0
  5. package/THEMING.md +130 -0
  6. package/core/headline/README.md +121 -0
  7. package/core/icon/README.md +173 -0
  8. package/core/image/README.md +210 -0
  9. package/core/link/README.md +297 -0
  10. package/core/separator/README.md +145 -0
  11. package/core/text/README.md +240 -0
  12. package/directives/infinite-scroll/README.md +102 -0
  13. package/directives/spotlight/README.md +154 -0
  14. package/directives/tooltip/README.md +147 -0
  15. package/docs/endpoint-link/README.md +142 -0
  16. package/docs/method-badge/README.md +154 -0
  17. package/fesm2022/frame-kit-ui-ng-core-headline.mjs +122 -0
  18. package/fesm2022/frame-kit-ui-ng-core-headline.mjs.map +1 -0
  19. package/fesm2022/frame-kit-ui-ng-core-icon.mjs +189 -0
  20. package/fesm2022/frame-kit-ui-ng-core-icon.mjs.map +1 -0
  21. package/fesm2022/frame-kit-ui-ng-core-image.mjs +123 -0
  22. package/fesm2022/frame-kit-ui-ng-core-image.mjs.map +1 -0
  23. package/fesm2022/frame-kit-ui-ng-core-link.mjs +369 -0
  24. package/fesm2022/frame-kit-ui-ng-core-link.mjs.map +1 -0
  25. package/fesm2022/frame-kit-ui-ng-core-separator.mjs +59 -0
  26. package/fesm2022/frame-kit-ui-ng-core-separator.mjs.map +1 -0
  27. package/fesm2022/frame-kit-ui-ng-core-text.mjs +204 -0
  28. package/fesm2022/frame-kit-ui-ng-core-text.mjs.map +1 -0
  29. package/fesm2022/frame-kit-ui-ng-directives-infinite-scroll.mjs +74 -0
  30. package/fesm2022/frame-kit-ui-ng-directives-infinite-scroll.mjs.map +1 -0
  31. package/fesm2022/frame-kit-ui-ng-directives-spotlight.mjs +76 -0
  32. package/fesm2022/frame-kit-ui-ng-directives-spotlight.mjs.map +1 -0
  33. package/fesm2022/frame-kit-ui-ng-directives-tooltip.mjs +425 -0
  34. package/fesm2022/frame-kit-ui-ng-directives-tooltip.mjs.map +1 -0
  35. package/fesm2022/frame-kit-ui-ng-docs-endpoint-link.mjs +63 -0
  36. package/fesm2022/frame-kit-ui-ng-docs-endpoint-link.mjs.map +1 -0
  37. package/fesm2022/frame-kit-ui-ng-docs-method-badge.mjs +43 -0
  38. package/fesm2022/frame-kit-ui-ng-docs-method-badge.mjs.map +1 -0
  39. package/fesm2022/frame-kit-ui-ng-forms.mjs +3632 -0
  40. package/fesm2022/frame-kit-ui-ng-forms.mjs.map +1 -0
  41. package/fesm2022/frame-kit-ui-ng-layouts-app-shell.mjs +239 -0
  42. package/fesm2022/frame-kit-ui-ng-layouts-app-shell.mjs.map +1 -0
  43. package/fesm2022/frame-kit-ui-ng-layouts-content-split.mjs +132 -0
  44. package/fesm2022/frame-kit-ui-ng-layouts-content-split.mjs.map +1 -0
  45. package/fesm2022/frame-kit-ui-ng-services-overlay-orchestrator.mjs +133 -0
  46. package/fesm2022/frame-kit-ui-ng-services-overlay-orchestrator.mjs.map +1 -0
  47. package/fesm2022/frame-kit-ui-ng-services-spotlight.mjs +60 -0
  48. package/fesm2022/frame-kit-ui-ng-services-spotlight.mjs.map +1 -0
  49. package/fesm2022/frame-kit-ui-ng-services-toast.mjs +166 -0
  50. package/fesm2022/frame-kit-ui-ng-services-toast.mjs.map +1 -0
  51. package/fesm2022/frame-kit-ui-ng-ui-accordion.mjs +214 -0
  52. package/fesm2022/frame-kit-ui-ng-ui-accordion.mjs.map +1 -0
  53. package/fesm2022/frame-kit-ui-ng-ui-alert.mjs +82 -0
  54. package/fesm2022/frame-kit-ui-ng-ui-alert.mjs.map +1 -0
  55. package/fesm2022/frame-kit-ui-ng-ui-avatar-stack.mjs +76 -0
  56. package/fesm2022/frame-kit-ui-ng-ui-avatar-stack.mjs.map +1 -0
  57. package/fesm2022/frame-kit-ui-ng-ui-avatar.mjs +81 -0
  58. package/fesm2022/frame-kit-ui-ng-ui-avatar.mjs.map +1 -0
  59. package/fesm2022/frame-kit-ui-ng-ui-badge.mjs +81 -0
  60. package/fesm2022/frame-kit-ui-ng-ui-badge.mjs.map +1 -0
  61. package/fesm2022/frame-kit-ui-ng-ui-breadcrumb.mjs +68 -0
  62. package/fesm2022/frame-kit-ui-ng-ui-breadcrumb.mjs.map +1 -0
  63. package/fesm2022/frame-kit-ui-ng-ui-button.mjs +108 -0
  64. package/fesm2022/frame-kit-ui-ng-ui-button.mjs.map +1 -0
  65. package/fesm2022/frame-kit-ui-ng-ui-callout.mjs +58 -0
  66. package/fesm2022/frame-kit-ui-ng-ui-callout.mjs.map +1 -0
  67. package/fesm2022/frame-kit-ui-ng-ui-card.mjs +70 -0
  68. package/fesm2022/frame-kit-ui-ng-ui-card.mjs.map +1 -0
  69. package/fesm2022/frame-kit-ui-ng-ui-copyable-field.mjs +113 -0
  70. package/fesm2022/frame-kit-ui-ng-ui-copyable-field.mjs.map +1 -0
  71. package/fesm2022/frame-kit-ui-ng-ui-data-table.mjs +1288 -0
  72. package/fesm2022/frame-kit-ui-ng-ui-data-table.mjs.map +1 -0
  73. package/fesm2022/frame-kit-ui-ng-ui-dialog.mjs +456 -0
  74. package/fesm2022/frame-kit-ui-ng-ui-dialog.mjs.map +1 -0
  75. package/fesm2022/frame-kit-ui-ng-ui-drawer.mjs +398 -0
  76. package/fesm2022/frame-kit-ui-ng-ui-drawer.mjs.map +1 -0
  77. package/fesm2022/frame-kit-ui-ng-ui-dropdown-menu.mjs +398 -0
  78. package/fesm2022/frame-kit-ui-ng-ui-dropdown-menu.mjs.map +1 -0
  79. package/fesm2022/frame-kit-ui-ng-ui-editable-field.mjs +125 -0
  80. package/fesm2022/frame-kit-ui-ng-ui-editable-field.mjs.map +1 -0
  81. package/fesm2022/frame-kit-ui-ng-ui-icon-badge.mjs +113 -0
  82. package/fesm2022/frame-kit-ui-ng-ui-icon-badge.mjs.map +1 -0
  83. package/fesm2022/frame-kit-ui-ng-ui-icon-list.mjs +111 -0
  84. package/fesm2022/frame-kit-ui-ng-ui-icon-list.mjs.map +1 -0
  85. package/fesm2022/frame-kit-ui-ng-ui-inline-edit.mjs +103 -0
  86. package/fesm2022/frame-kit-ui-ng-ui-inline-edit.mjs.map +1 -0
  87. package/fesm2022/frame-kit-ui-ng-ui-list-editor.mjs +135 -0
  88. package/fesm2022/frame-kit-ui-ng-ui-list-editor.mjs.map +1 -0
  89. package/fesm2022/frame-kit-ui-ng-ui-loader.mjs +81 -0
  90. package/fesm2022/frame-kit-ui-ng-ui-loader.mjs.map +1 -0
  91. package/fesm2022/frame-kit-ui-ng-ui-menu-item.mjs +79 -0
  92. package/fesm2022/frame-kit-ui-ng-ui-menu-item.mjs.map +1 -0
  93. package/fesm2022/frame-kit-ui-ng-ui-nav-brand.mjs +40 -0
  94. package/fesm2022/frame-kit-ui-ng-ui-nav-brand.mjs.map +1 -0
  95. package/fesm2022/frame-kit-ui-ng-ui-nav-group.mjs +110 -0
  96. package/fesm2022/frame-kit-ui-ng-ui-nav-group.mjs.map +1 -0
  97. package/fesm2022/frame-kit-ui-ng-ui-nav-separator.mjs +91 -0
  98. package/fesm2022/frame-kit-ui-ng-ui-nav-separator.mjs.map +1 -0
  99. package/fesm2022/frame-kit-ui-ng-ui-node-tree-breadcrumb.mjs +86 -0
  100. package/fesm2022/frame-kit-ui-ng-ui-node-tree-breadcrumb.mjs.map +1 -0
  101. package/fesm2022/frame-kit-ui-ng-ui-node-tree.mjs +443 -0
  102. package/fesm2022/frame-kit-ui-ng-ui-node-tree.mjs.map +1 -0
  103. package/fesm2022/frame-kit-ui-ng-ui-note.mjs +56 -0
  104. package/fesm2022/frame-kit-ui-ng-ui-note.mjs.map +1 -0
  105. package/fesm2022/frame-kit-ui-ng-ui-numbered-list.mjs +105 -0
  106. package/fesm2022/frame-kit-ui-ng-ui-numbered-list.mjs.map +1 -0
  107. package/fesm2022/frame-kit-ui-ng-ui-pagination.mjs +110 -0
  108. package/fesm2022/frame-kit-ui-ng-ui-pagination.mjs.map +1 -0
  109. package/fesm2022/frame-kit-ui-ng-ui-progress-bar.mjs +129 -0
  110. package/fesm2022/frame-kit-ui-ng-ui-progress-bar.mjs.map +1 -0
  111. package/fesm2022/frame-kit-ui-ng-ui-sidenav-link.mjs +42 -0
  112. package/fesm2022/frame-kit-ui-ng-ui-sidenav-link.mjs.map +1 -0
  113. package/fesm2022/frame-kit-ui-ng-ui-tabs.mjs +894 -0
  114. package/fesm2022/frame-kit-ui-ng-ui-tabs.mjs.map +1 -0
  115. package/fesm2022/frame-kit-ui-ng-ui-timeline.mjs +81 -0
  116. package/fesm2022/frame-kit-ui-ng-ui-timeline.mjs.map +1 -0
  117. package/fesm2022/frame-kit-ui-ng-ui-toast.mjs +179 -0
  118. package/fesm2022/frame-kit-ui-ng-ui-toast.mjs.map +1 -0
  119. package/fesm2022/frame-kit-ui-ng-ui-user-menu.mjs +143 -0
  120. package/fesm2022/frame-kit-ui-ng-ui-user-menu.mjs.map +1 -0
  121. package/fesm2022/frame-kit-ui-ng-ui-wizard-dialog.mjs +191 -0
  122. package/fesm2022/frame-kit-ui-ng-ui-wizard-dialog.mjs.map +1 -0
  123. package/fesm2022/frame-kit-ui-ng.mjs +58 -0
  124. package/fesm2022/frame-kit-ui-ng.mjs.map +1 -0
  125. package/layouts/app-shell/README.md +357 -0
  126. package/layouts/content-split/README.md +180 -0
  127. package/package.json +253 -0
  128. package/services/overlay-orchestrator/README.md +184 -0
  129. package/services/spotlight/README.md +61 -0
  130. package/services/toast/README.md +118 -0
  131. package/types/frame-kit-ui-ng-core-headline.d.ts +38 -0
  132. package/types/frame-kit-ui-ng-core-icon.d.ts +74 -0
  133. package/types/frame-kit-ui-ng-core-image.d.ts +93 -0
  134. package/types/frame-kit-ui-ng-core-link.d.ts +251 -0
  135. package/types/frame-kit-ui-ng-core-separator.d.ts +28 -0
  136. package/types/frame-kit-ui-ng-core-text.d.ts +186 -0
  137. package/types/frame-kit-ui-ng-directives-infinite-scroll.d.ts +42 -0
  138. package/types/frame-kit-ui-ng-directives-spotlight.d.ts +51 -0
  139. package/types/frame-kit-ui-ng-directives-tooltip.d.ts +70 -0
  140. package/types/frame-kit-ui-ng-docs-endpoint-link.d.ts +43 -0
  141. package/types/frame-kit-ui-ng-docs-method-badge.d.ts +30 -0
  142. package/types/frame-kit-ui-ng-forms.d.ts +1674 -0
  143. package/types/frame-kit-ui-ng-layouts-app-shell.d.ts +75 -0
  144. package/types/frame-kit-ui-ng-layouts-content-split.d.ts +43 -0
  145. package/types/frame-kit-ui-ng-services-overlay-orchestrator.d.ts +96 -0
  146. package/types/frame-kit-ui-ng-services-spotlight.d.ts +32 -0
  147. package/types/frame-kit-ui-ng-services-toast.d.ts +100 -0
  148. package/types/frame-kit-ui-ng-ui-accordion.d.ts +86 -0
  149. package/types/frame-kit-ui-ng-ui-alert.d.ts +34 -0
  150. package/types/frame-kit-ui-ng-ui-avatar-stack.d.ts +38 -0
  151. package/types/frame-kit-ui-ng-ui-avatar.d.ts +36 -0
  152. package/types/frame-kit-ui-ng-ui-badge.d.ts +33 -0
  153. package/types/frame-kit-ui-ng-ui-breadcrumb.d.ts +45 -0
  154. package/types/frame-kit-ui-ng-ui-button.d.ts +48 -0
  155. package/types/frame-kit-ui-ng-ui-callout.d.ts +26 -0
  156. package/types/frame-kit-ui-ng-ui-card.d.ts +30 -0
  157. package/types/frame-kit-ui-ng-ui-copyable-field.d.ts +62 -0
  158. package/types/frame-kit-ui-ng-ui-data-table.d.ts +482 -0
  159. package/types/frame-kit-ui-ng-ui-dialog.d.ts +166 -0
  160. package/types/frame-kit-ui-ng-ui-drawer.d.ts +130 -0
  161. package/types/frame-kit-ui-ng-ui-dropdown-menu.d.ts +77 -0
  162. package/types/frame-kit-ui-ng-ui-editable-field.d.ts +65 -0
  163. package/types/frame-kit-ui-ng-ui-icon-badge.d.ts +45 -0
  164. package/types/frame-kit-ui-ng-ui-icon-list.d.ts +67 -0
  165. package/types/frame-kit-ui-ng-ui-inline-edit.d.ts +44 -0
  166. package/types/frame-kit-ui-ng-ui-list-editor.d.ts +56 -0
  167. package/types/frame-kit-ui-ng-ui-loader.d.ts +32 -0
  168. package/types/frame-kit-ui-ng-ui-menu-item.d.ts +27 -0
  169. package/types/frame-kit-ui-ng-ui-nav-brand.d.ts +25 -0
  170. package/types/frame-kit-ui-ng-ui-nav-group.d.ts +60 -0
  171. package/types/frame-kit-ui-ng-ui-nav-separator.d.ts +33 -0
  172. package/types/frame-kit-ui-ng-ui-node-tree-breadcrumb.d.ts +35 -0
  173. package/types/frame-kit-ui-ng-ui-node-tree.d.ts +135 -0
  174. package/types/frame-kit-ui-ng-ui-note.d.ts +22 -0
  175. package/types/frame-kit-ui-ng-ui-numbered-list.d.ts +52 -0
  176. package/types/frame-kit-ui-ng-ui-pagination.d.ts +49 -0
  177. package/types/frame-kit-ui-ng-ui-progress-bar.d.ts +50 -0
  178. package/types/frame-kit-ui-ng-ui-sidenav-link.d.ts +24 -0
  179. package/types/frame-kit-ui-ng-ui-tabs.d.ts +266 -0
  180. package/types/frame-kit-ui-ng-ui-timeline.d.ts +42 -0
  181. package/types/frame-kit-ui-ng-ui-toast.d.ts +56 -0
  182. package/types/frame-kit-ui-ng-ui-user-menu.d.ts +87 -0
  183. package/types/frame-kit-ui-ng-ui-wizard-dialog.d.ts +116 -0
  184. package/types/frame-kit-ui-ng.d.ts +53 -0
  185. package/ui/accordion/README.md +261 -0
  186. package/ui/alert/README.md +211 -0
  187. package/ui/avatar/README.md +167 -0
  188. package/ui/avatar-stack/README.md +164 -0
  189. package/ui/badge/README.md +162 -0
  190. package/ui/breadcrumb/README.md +240 -0
  191. package/ui/button/README.md +184 -0
  192. package/ui/callout/README.md +159 -0
  193. package/ui/card/README.md +174 -0
  194. package/ui/copyable-field/README.md +235 -0
  195. package/ui/data-table/README.md +408 -0
  196. package/ui/dialog/README.md +222 -0
  197. package/ui/drawer/README.md +274 -0
  198. package/ui/dropdown-menu/README.md +336 -0
  199. package/ui/editable-field/README.md +171 -0
  200. package/ui/icon-badge/README.md +131 -0
  201. package/ui/icon-list/README.md +205 -0
  202. package/ui/inline-edit/README.md +135 -0
  203. package/ui/list-editor/README.md +162 -0
  204. package/ui/loader/README.md +160 -0
  205. package/ui/menu-item/README.md +204 -0
  206. package/ui/nav-brand/README.md +111 -0
  207. package/ui/nav-group/README.md +145 -0
  208. package/ui/nav-separator/README.md +44 -0
  209. package/ui/node-tree/README.md +278 -0
  210. package/ui/node-tree-breadcrumb/README.md +164 -0
  211. package/ui/note/README.md +146 -0
  212. package/ui/numbered-list/README.md +187 -0
  213. package/ui/pagination/README.md +174 -0
  214. package/ui/progress-bar/README.md +223 -0
  215. package/ui/sidenav-link/README.md +214 -0
  216. package/ui/tabs/README.md +204 -0
  217. package/ui/timeline/README.md +285 -0
  218. package/ui/toast/README.md +243 -0
  219. package/ui/user-menu/README.md +260 -0
  220. package/ui/wizard-dialog/README.md +283 -0
@@ -0,0 +1,239 @@
1
+ import { BreakpointObserver } from '@angular/cdk/layout';
2
+ import * as i0 from '@angular/core';
3
+ import { input, inject, signal, computed, DestroyRef, afterNextRender, HostBinding, ChangeDetectionStrategy, Component } from '@angular/core';
4
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
+ import { OverlayOrchestrator, OVERLAY_PRIORITY } from '@frame-kit/ui-ng/services/overlay-orchestrator';
6
+
7
+ class AppShellComponent {
8
+ // ===== INPUTS =====
9
+ /** Layout mode for the primary sidenav — `"side"` pushes content, `"over"` floats above it, `"icon"` collapses to an icon rail. */
10
+ mode = input('side', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
11
+ /** Width of the primary sidenav when fully open. */
12
+ sidenavWidth = input('260px', ...(ngDevMode ? [{ debugName: "sidenavWidth" }] : /* istanbul ignore next */ []));
13
+ /** Width of the primary sidenav when collapsed to icon-rail mode. */
14
+ collapsedWidth = input('64px', ...(ngDevMode ? [{ debugName: "collapsedWidth" }] : /* istanbul ignore next */ []));
15
+ /** CSS media query used to determine mobile breakpoint for the primary sidenav. */
16
+ breakpoint = input('(min-width: 48em)', ...(ngDevMode ? [{ debugName: "breakpoint" }] : /* istanbul ignore next */ []));
17
+ /** When true, the main header sticks to the top of the viewport during scroll. */
18
+ sticky = input(true, ...(ngDevMode ? [{ debugName: "sticky" }] : /* istanbul ignore next */ []));
19
+ /** When true and on mobile, the sidenav collapses to the icon rail instead of hiding entirely. */
20
+ collapseOnMobile = input(false, ...(ngDevMode ? [{ debugName: "collapseOnMobile" }] : /* istanbul ignore next */ []));
21
+ /** Layout mode for the end (trailing) panel. */
22
+ endMode = input('over', ...(ngDevMode ? [{ debugName: "endMode" }] : /* istanbul ignore next */ []));
23
+ /** Width of the end panel when fully open. */
24
+ endWidth = input('320px', ...(ngDevMode ? [{ debugName: "endWidth" }] : /* istanbul ignore next */ []));
25
+ /** Width of the end panel when collapsed to icon-rail mode. */
26
+ endCollapsedWidth = input('64px', ...(ngDevMode ? [{ debugName: "endCollapsedWidth" }] : /* istanbul ignore next */ []));
27
+ orchestrator = inject(OverlayOrchestrator);
28
+ navOverlayId = null;
29
+ endOverlayId = null;
30
+ // ===== INTERNAL STATE =====
31
+ stickyPreference = signal(null, ...(ngDevMode ? [{ debugName: "stickyPreference" }] : /* istanbul ignore next */ []));
32
+ tempOpen = signal(false, ...(ngDevMode ? [{ debugName: "tempOpen" }] : /* istanbul ignore next */ []));
33
+ isMobile = signal(false, ...(ngDevMode ? [{ debugName: "isMobile" }] : /* istanbul ignore next */ []));
34
+ endStickyPreference = signal('closed', ...(ngDevMode ? [{ debugName: "endStickyPreference" }] : /* istanbul ignore next */ []));
35
+ endTempOpen = signal(false, ...(ngDevMode ? [{ debugName: "endTempOpen" }] : /* istanbul ignore next */ []));
36
+ /**
37
+ * Transitions are opt-in: only enabled after a user action (toggle, open,
38
+ * close, dismiss). Viewport changes clear this flag so layout shifts are
39
+ * always instant — no flash of the aside sliding in/out on resize.
40
+ */
41
+ animate = signal(false, ...(ngDevMode ? [{ debugName: "animate" }] : /* istanbul ignore next */ []));
42
+ // ===== COMPUTED =====
43
+ sidenavOpen = computed(() => {
44
+ if (this.stickyPreference() === 'closed') {
45
+ return false;
46
+ }
47
+ if (this.tempOpen()) {
48
+ return true;
49
+ }
50
+ return !this.isMobile();
51
+ }, ...(ngDevMode ? [{ debugName: "sidenavOpen" }] : /* istanbul ignore next */ []));
52
+ showOverlay = computed(() => {
53
+ if (!this.sidenavOpen()) {
54
+ return false;
55
+ }
56
+ if (this.mode() === 'over') {
57
+ return true;
58
+ }
59
+ return this.isMobile();
60
+ }, ...(ngDevMode ? [{ debugName: "showOverlay" }] : /* istanbul ignore next */ []));
61
+ isCollapsed = computed(() => {
62
+ if (this.mode() !== 'icon') {
63
+ return false;
64
+ }
65
+ if (this.sidenavOpen()) {
66
+ return false;
67
+ }
68
+ if (this.isMobile()) {
69
+ return this.collapseOnMobile();
70
+ }
71
+ return true;
72
+ }, ...(ngDevMode ? [{ debugName: "isCollapsed" }] : /* istanbul ignore next */ []));
73
+ endOpen = computed(() => {
74
+ if (this.endStickyPreference() === 'closed') {
75
+ return false;
76
+ }
77
+ if (this.endTempOpen()) {
78
+ return true;
79
+ }
80
+ return !this.isMobile();
81
+ }, ...(ngDevMode ? [{ debugName: "endOpen" }] : /* istanbul ignore next */ []));
82
+ showEndOverlay = computed(() => {
83
+ if (!this.endOpen()) {
84
+ return false;
85
+ }
86
+ if (this.endMode() === 'over') {
87
+ return true;
88
+ }
89
+ return this.isMobile();
90
+ }, ...(ngDevMode ? [{ debugName: "showEndOverlay" }] : /* istanbul ignore next */ []));
91
+ isEndCollapsed = computed(() => {
92
+ return this.endMode() === 'icon' && !this.endOpen() && !this.isMobile();
93
+ }, ...(ngDevMode ? [{ debugName: "isEndCollapsed" }] : /* istanbul ignore next */ []));
94
+ classes = computed(() => {
95
+ const mode = this.mode();
96
+ const endMode = this.endMode();
97
+ return [
98
+ 'fk-app-shell',
99
+ `fk-app-shell--${mode}`,
100
+ this.sticky() ? 'fk-app-shell--sticky' : '',
101
+ this.sidenavOpen() ? 'fk-app-shell--open' : '',
102
+ this.isCollapsed() ? 'fk-app-shell--collapsed' : '',
103
+ this.isMobile() ? 'fk-app-shell--mobile' : '',
104
+ this.animate() ? 'fk-app-shell--animate' : '',
105
+ `fk-app-shell--end-${endMode}`,
106
+ this.endOpen() ? 'fk-app-shell--end-open' : '',
107
+ this.isEndCollapsed() ? 'fk-app-shell--end-collapsed' : '',
108
+ ]
109
+ .filter(Boolean)
110
+ .join(' ');
111
+ }, ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
112
+ get hostStyle() {
113
+ return {
114
+ '--fk-app-shell-sidenav-width': this.sidenavWidth(),
115
+ '--fk-app-shell-collapsed-width': this.collapsedWidth(),
116
+ '--fk-app-shell-end-width': this.endWidth(),
117
+ '--fk-app-shell-end-collapsed-width': this.endCollapsedWidth(),
118
+ };
119
+ }
120
+ // ===== BREAKPOINT =====
121
+ breakpointObserver = inject(BreakpointObserver);
122
+ destroyRef = inject(DestroyRef);
123
+ constructor() {
124
+ // Defer the breakpoint subscription until the first client-side render.
125
+ // During SSR, CDK's MediaMatcher returns `{ matches: false }` for every
126
+ // query, which would flip `isMobile` to true and produce a mobile-layout
127
+ // HTML in the server output, causing a visible flash of the overlay
128
+ // sidebar before the client corrects it on hydration. `afterNextRender`
129
+ // only runs in the browser, so `isMobile` stays at its `false` default
130
+ // through SSR and the real viewport check happens post-hydration.
131
+ afterNextRender(() => {
132
+ this.breakpointObserver
133
+ .observe(this.breakpoint())
134
+ .pipe(takeUntilDestroyed(this.destroyRef))
135
+ .subscribe((result) => {
136
+ this.animate.set(false);
137
+ this.isMobile.set(!result.matches);
138
+ this.tempOpen.set(false);
139
+ this.endTempOpen.set(false);
140
+ });
141
+ });
142
+ }
143
+ // ===== METHODS =====
144
+ /** Open the primary sidenav, registering it with the overlay orchestrator on mobile. */
145
+ openSidenav() {
146
+ this.animate.set(true);
147
+ this.stickyPreference.set(null);
148
+ if (this.isMobile()) {
149
+ this.tempOpen.set(true);
150
+ this.navOverlayId = this.orchestrator.register({
151
+ priority: OVERLAY_PRIORITY.NAVIGATION,
152
+ type: 'navigation',
153
+ close: () => this.closeSidenav(),
154
+ });
155
+ }
156
+ }
157
+ /** Close the primary sidenav and unregister it from the overlay orchestrator. */
158
+ closeSidenav() {
159
+ this.animate.set(true);
160
+ this.stickyPreference.set('closed');
161
+ this.tempOpen.set(false);
162
+ if (this.navOverlayId) {
163
+ this.orchestrator.unregister(this.navOverlayId);
164
+ this.navOverlayId = null;
165
+ }
166
+ }
167
+ /** Toggle the primary sidenav between open and closed. */
168
+ toggleSidenav() {
169
+ if (this.sidenavOpen()) {
170
+ this.closeSidenav();
171
+ }
172
+ else {
173
+ this.openSidenav();
174
+ }
175
+ }
176
+ /** Dismiss the sidenav overlay on mobile without marking it as permanently closed. */
177
+ dismissSidenav() {
178
+ this.animate.set(true);
179
+ this.tempOpen.set(false);
180
+ }
181
+ /** Open the end panel, registering it with the overlay orchestrator on mobile. */
182
+ openEnd() {
183
+ this.animate.set(true);
184
+ this.endStickyPreference.set(null);
185
+ if (this.isMobile()) {
186
+ this.endTempOpen.set(true);
187
+ this.endOverlayId = this.orchestrator.register({
188
+ priority: OVERLAY_PRIORITY.NAVIGATION,
189
+ type: 'navigation',
190
+ close: () => this.closeEnd(),
191
+ });
192
+ }
193
+ }
194
+ /** Close the end panel and unregister it from the overlay orchestrator. */
195
+ closeEnd() {
196
+ this.animate.set(true);
197
+ this.endStickyPreference.set('closed');
198
+ this.endTempOpen.set(false);
199
+ if (this.endOverlayId) {
200
+ this.orchestrator.unregister(this.endOverlayId);
201
+ this.endOverlayId = null;
202
+ }
203
+ }
204
+ /** Toggle the end panel between open and closed. */
205
+ toggleEnd() {
206
+ if (this.endOpen()) {
207
+ this.closeEnd();
208
+ }
209
+ else {
210
+ this.openEnd();
211
+ }
212
+ }
213
+ /** Dismiss the end panel overlay and unregister it from the overlay orchestrator. */
214
+ dismissEnd() {
215
+ this.animate.set(true);
216
+ this.endStickyPreference.set('closed');
217
+ this.endTempOpen.set(false);
218
+ if (this.endOverlayId) {
219
+ this.orchestrator.unregister(this.endOverlayId);
220
+ this.endOverlayId = null;
221
+ }
222
+ }
223
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AppShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
224
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AppShellComponent, isStandalone: true, selector: "fk-app-shell", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, sidenavWidth: { classPropertyName: "sidenavWidth", publicName: "sidenavWidth", isSignal: true, isRequired: false, transformFunction: null }, collapsedWidth: { classPropertyName: "collapsedWidth", publicName: "collapsedWidth", isSignal: true, isRequired: false, transformFunction: null }, breakpoint: { classPropertyName: "breakpoint", publicName: "breakpoint", isSignal: true, isRequired: false, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null }, collapseOnMobile: { classPropertyName: "collapseOnMobile", publicName: "collapseOnMobile", isSignal: true, isRequired: false, transformFunction: null }, endMode: { classPropertyName: "endMode", publicName: "endMode", isSignal: true, isRequired: false, transformFunction: null }, endWidth: { classPropertyName: "endWidth", publicName: "endWidth", isSignal: true, isRequired: false, transformFunction: null }, endCollapsedWidth: { classPropertyName: "endCollapsedWidth", publicName: "endCollapsedWidth", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style": "this.hostStyle" } }, ngImport: i0, template: "<div [class]=\"classes()\">\n <header class=\"fk-app-shell__header\">\n <ng-content select=\"[appShellHeader]\" />\n </header>\n\n <div class=\"fk-app-shell__body\">\n @if (showOverlay()) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div class=\"fk-app-shell__overlay\" (click)=\"dismissSidenav()\"></div>\n }\n\n <aside\n class=\"fk-app-shell__aside\"\n [class.fk-app-shell__aside--open]=\"sidenavOpen()\"\n [class.fk-app-shell__aside--collapsed]=\"isCollapsed()\"\n >\n <ng-content select=\"[appShellSidenav]\" />\n </aside>\n\n <main class=\"fk-app-shell__content\">\n <ng-content select=\"[appShellContent]\" />\n </main>\n\n @if (showEndOverlay()) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div class=\"fk-app-shell__end-overlay\" (click)=\"dismissEnd()\"></div>\n }\n\n <aside\n class=\"fk-app-shell__end\"\n [class.fk-app-shell__end--open]=\"endOpen()\"\n [class.fk-app-shell__end--collapsed]=\"isEndCollapsed()\"\n >\n <ng-content select=\"[appShellEnd]\" />\n </aside>\n </div>\n\n <footer class=\"fk-app-shell__footer\">\n <ng-content select=\"[appShellFooter]\" />\n </footer>\n</div>\n", styles: [":host{display:block;height:100%;flex:1;min-height:0}.fk-app-shell{display:flex;flex-direction:column;height:100%;overflow:hidden}.fk-app-shell--animate .fk-app-shell__aside,.fk-app-shell--animate .fk-app-shell__end{transition:width .25s cubic-bezier(.4,0,.2,1),transform .25s cubic-bezier(.4,0,.2,1)}.fk-app-shell__header{position:relative;z-index:var(--fk-app-shell-header-z-index, 100);flex-shrink:0}.fk-app-shell__body{display:flex;flex:1;position:relative;min-height:0}.fk-app-shell__overlay{position:fixed;inset:0;z-index:var(--fk-app-shell-overlay-z-index, 200);background:var(--fk-app-shell-overlay-bg, var(--fk-black-50, rgba(0, 0, 0, .5)))}.fk-app-shell__aside{display:flex;flex-direction:column;width:var(--fk-app-shell-sidenav-width);flex-shrink:0;overflow:hidden}.fk-app-shell__content{flex:1;min-width:0;overflow-y:auto;overscroll-behavior:contain}.fk-app-shell__footer{flex-shrink:0}.fk-app-shell--side .fk-app-shell__aside{position:relative}.fk-app-shell--side:not(.fk-app-shell--mobile):not(.fk-app-shell--open) .fk-app-shell__aside{width:0;overflow:hidden}.fk-app-shell--mobile .fk-app-shell__aside,.fk-app-shell--over .fk-app-shell__aside{position:fixed;top:0;left:0;bottom:0;z-index:var(--fk-app-shell-aside-z-index, 201);width:var(--fk-app-shell-sidenav-width);transform:translate(-100%);background:var(--fk-app-shell-aside-bg, var(--fk-color-surface, #ffffff))}.fk-app-shell--mobile .fk-app-shell__aside{width:var(--fk-app-shell-mobile-sidenav-width, 100%)}.fk-app-shell--mobile .fk-app-shell__aside--open,.fk-app-shell--over .fk-app-shell__aside--open{transform:translate(0)}.fk-app-shell--icon:not(.fk-app-shell--mobile) .fk-app-shell__aside{position:relative}.fk-app-shell--icon:not(.fk-app-shell--mobile) .fk-app-shell__aside--collapsed{width:var(--fk-app-shell-collapsed-width)}.fk-app-shell--mobile .fk-app-shell__aside--collapsed{position:relative;top:auto;left:auto;bottom:auto;width:var(--fk-app-shell-collapsed-width);transform:none;z-index:auto}.fk-app-shell__end-overlay{position:fixed;inset:0;z-index:var(--fk-app-shell-overlay-z-index, 200);background:var(--fk-app-shell-overlay-bg, var(--fk-black-50, rgba(0, 0, 0, .5)))}.fk-app-shell__end{display:flex;flex-direction:column;width:var(--fk-app-shell-end-width);flex-shrink:0;overflow:hidden;background:var(--fk-app-shell-end-bg, var(--fk-color-surface, #ffffff));border-radius:var(--fk-app-shell-end-radius, 0)}.fk-app-shell--end-side .fk-app-shell__end{position:relative}.fk-app-shell--end-side:not(.fk-app-shell--mobile):not(.fk-app-shell--end-open) .fk-app-shell__end{width:0;overflow:hidden}.fk-app-shell--mobile .fk-app-shell__end,.fk-app-shell--end-over .fk-app-shell__end{position:fixed;top:0;right:0;bottom:0;z-index:var(--fk-app-shell-end-z-index, 201);width:var(--fk-app-shell-end-width);transform:translate(100%);background:var(--fk-app-shell-end-bg, var(--fk-color-surface, #ffffff))}.fk-app-shell--mobile .fk-app-shell__end--open,.fk-app-shell--end-over .fk-app-shell__end--open{box-shadow:var(--fk-app-shell-end-shadow, -4px 0 16px rgba(0, 0, 0, .08))}.fk-app-shell--mobile .fk-app-shell__end--open,.fk-app-shell--end-over .fk-app-shell__end--open{transform:translate(0)}.fk-app-shell--end-icon:not(.fk-app-shell--mobile) .fk-app-shell__end{position:relative}.fk-app-shell--end-icon:not(.fk-app-shell--mobile) .fk-app-shell__end--collapsed{width:var(--fk-app-shell-end-collapsed-width)}@media(prefers-reduced-motion:reduce){.fk-app-shell--animate .fk-app-shell__aside,.fk-app-shell--animate .fk-app-shell__end{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
225
+ }
226
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AppShellComponent, decorators: [{
227
+ type: Component,
228
+ args: [{ selector: 'fk-app-shell', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div [class]=\"classes()\">\n <header class=\"fk-app-shell__header\">\n <ng-content select=\"[appShellHeader]\" />\n </header>\n\n <div class=\"fk-app-shell__body\">\n @if (showOverlay()) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div class=\"fk-app-shell__overlay\" (click)=\"dismissSidenav()\"></div>\n }\n\n <aside\n class=\"fk-app-shell__aside\"\n [class.fk-app-shell__aside--open]=\"sidenavOpen()\"\n [class.fk-app-shell__aside--collapsed]=\"isCollapsed()\"\n >\n <ng-content select=\"[appShellSidenav]\" />\n </aside>\n\n <main class=\"fk-app-shell__content\">\n <ng-content select=\"[appShellContent]\" />\n </main>\n\n @if (showEndOverlay()) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div class=\"fk-app-shell__end-overlay\" (click)=\"dismissEnd()\"></div>\n }\n\n <aside\n class=\"fk-app-shell__end\"\n [class.fk-app-shell__end--open]=\"endOpen()\"\n [class.fk-app-shell__end--collapsed]=\"isEndCollapsed()\"\n >\n <ng-content select=\"[appShellEnd]\" />\n </aside>\n </div>\n\n <footer class=\"fk-app-shell__footer\">\n <ng-content select=\"[appShellFooter]\" />\n </footer>\n</div>\n", styles: [":host{display:block;height:100%;flex:1;min-height:0}.fk-app-shell{display:flex;flex-direction:column;height:100%;overflow:hidden}.fk-app-shell--animate .fk-app-shell__aside,.fk-app-shell--animate .fk-app-shell__end{transition:width .25s cubic-bezier(.4,0,.2,1),transform .25s cubic-bezier(.4,0,.2,1)}.fk-app-shell__header{position:relative;z-index:var(--fk-app-shell-header-z-index, 100);flex-shrink:0}.fk-app-shell__body{display:flex;flex:1;position:relative;min-height:0}.fk-app-shell__overlay{position:fixed;inset:0;z-index:var(--fk-app-shell-overlay-z-index, 200);background:var(--fk-app-shell-overlay-bg, var(--fk-black-50, rgba(0, 0, 0, .5)))}.fk-app-shell__aside{display:flex;flex-direction:column;width:var(--fk-app-shell-sidenav-width);flex-shrink:0;overflow:hidden}.fk-app-shell__content{flex:1;min-width:0;overflow-y:auto;overscroll-behavior:contain}.fk-app-shell__footer{flex-shrink:0}.fk-app-shell--side .fk-app-shell__aside{position:relative}.fk-app-shell--side:not(.fk-app-shell--mobile):not(.fk-app-shell--open) .fk-app-shell__aside{width:0;overflow:hidden}.fk-app-shell--mobile .fk-app-shell__aside,.fk-app-shell--over .fk-app-shell__aside{position:fixed;top:0;left:0;bottom:0;z-index:var(--fk-app-shell-aside-z-index, 201);width:var(--fk-app-shell-sidenav-width);transform:translate(-100%);background:var(--fk-app-shell-aside-bg, var(--fk-color-surface, #ffffff))}.fk-app-shell--mobile .fk-app-shell__aside{width:var(--fk-app-shell-mobile-sidenav-width, 100%)}.fk-app-shell--mobile .fk-app-shell__aside--open,.fk-app-shell--over .fk-app-shell__aside--open{transform:translate(0)}.fk-app-shell--icon:not(.fk-app-shell--mobile) .fk-app-shell__aside{position:relative}.fk-app-shell--icon:not(.fk-app-shell--mobile) .fk-app-shell__aside--collapsed{width:var(--fk-app-shell-collapsed-width)}.fk-app-shell--mobile .fk-app-shell__aside--collapsed{position:relative;top:auto;left:auto;bottom:auto;width:var(--fk-app-shell-collapsed-width);transform:none;z-index:auto}.fk-app-shell__end-overlay{position:fixed;inset:0;z-index:var(--fk-app-shell-overlay-z-index, 200);background:var(--fk-app-shell-overlay-bg, var(--fk-black-50, rgba(0, 0, 0, .5)))}.fk-app-shell__end{display:flex;flex-direction:column;width:var(--fk-app-shell-end-width);flex-shrink:0;overflow:hidden;background:var(--fk-app-shell-end-bg, var(--fk-color-surface, #ffffff));border-radius:var(--fk-app-shell-end-radius, 0)}.fk-app-shell--end-side .fk-app-shell__end{position:relative}.fk-app-shell--end-side:not(.fk-app-shell--mobile):not(.fk-app-shell--end-open) .fk-app-shell__end{width:0;overflow:hidden}.fk-app-shell--mobile .fk-app-shell__end,.fk-app-shell--end-over .fk-app-shell__end{position:fixed;top:0;right:0;bottom:0;z-index:var(--fk-app-shell-end-z-index, 201);width:var(--fk-app-shell-end-width);transform:translate(100%);background:var(--fk-app-shell-end-bg, var(--fk-color-surface, #ffffff))}.fk-app-shell--mobile .fk-app-shell__end--open,.fk-app-shell--end-over .fk-app-shell__end--open{box-shadow:var(--fk-app-shell-end-shadow, -4px 0 16px rgba(0, 0, 0, .08))}.fk-app-shell--mobile .fk-app-shell__end--open,.fk-app-shell--end-over .fk-app-shell__end--open{transform:translate(0)}.fk-app-shell--end-icon:not(.fk-app-shell--mobile) .fk-app-shell__end{position:relative}.fk-app-shell--end-icon:not(.fk-app-shell--mobile) .fk-app-shell__end--collapsed{width:var(--fk-app-shell-end-collapsed-width)}@media(prefers-reduced-motion:reduce){.fk-app-shell--animate .fk-app-shell__aside,.fk-app-shell--animate .fk-app-shell__end{transition:none}}\n"] }]
229
+ }], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], sidenavWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidenavWidth", required: false }] }], collapsedWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsedWidth", required: false }] }], breakpoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "breakpoint", required: false }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }], collapseOnMobile: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapseOnMobile", required: false }] }], endMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "endMode", required: false }] }], endWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "endWidth", required: false }] }], endCollapsedWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "endCollapsedWidth", required: false }] }], hostStyle: [{
230
+ type: HostBinding,
231
+ args: ['style']
232
+ }] } });
233
+
234
+ /**
235
+ * Generated bundle index. Do not edit.
236
+ */
237
+
238
+ export { AppShellComponent };
239
+ //# sourceMappingURL=frame-kit-ui-ng-layouts-app-shell.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-kit-ui-ng-layouts-app-shell.mjs","sources":["../../../../packages/ui-ng/layouts/app-shell/app-shell.component.ts","../../../../packages/ui-ng/layouts/app-shell/app-shell.component.html","../../../../packages/ui-ng/layouts/app-shell/frame-kit-ui-ng-layouts-app-shell.ts"],"sourcesContent":["import { BreakpointObserver } from '@angular/cdk/layout';\nimport {\n afterNextRender,\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n HostBinding,\n inject,\n input,\n signal,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { OverlayOrchestrator } from '@frame-kit/ui-ng/services/overlay-orchestrator';\nimport { OVERLAY_PRIORITY } from '@frame-kit/ui-ng/services/overlay-orchestrator';\nimport type { AppShellMode } from './app-shell.types';\n\n@Component({\n selector: 'fk-app-shell',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './app-shell.component.html',\n styleUrl: './app-shell.component.scss',\n})\nexport class AppShellComponent {\n // ===== INPUTS =====\n /** Layout mode for the primary sidenav — `\"side\"` pushes content, `\"over\"` floats above it, `\"icon\"` collapses to an icon rail. */\n readonly mode = input<AppShellMode>('side');\n /** Width of the primary sidenav when fully open. */\n readonly sidenavWidth = input<string>('260px');\n /** Width of the primary sidenav when collapsed to icon-rail mode. */\n readonly collapsedWidth = input<string>('64px');\n /** CSS media query used to determine mobile breakpoint for the primary sidenav. */\n readonly breakpoint = input<string>('(min-width: 48em)');\n /** When true, the main header sticks to the top of the viewport during scroll. */\n readonly sticky = input<boolean>(true);\n /** When true and on mobile, the sidenav collapses to the icon rail instead of hiding entirely. */\n readonly collapseOnMobile = input<boolean>(false);\n\n /** Layout mode for the end (trailing) panel. */\n readonly endMode = input<AppShellMode>('over');\n /** Width of the end panel when fully open. */\n readonly endWidth = input<string>('320px');\n /** Width of the end panel when collapsed to icon-rail mode. */\n readonly endCollapsedWidth = input<string>('64px');\n\n private readonly orchestrator = inject(OverlayOrchestrator);\n private navOverlayId: string | null = null;\n private endOverlayId: string | null = null;\n\n // ===== INTERNAL STATE =====\n readonly stickyPreference = signal<'closed' | null>(null);\n readonly tempOpen = signal<boolean>(false);\n readonly isMobile = signal<boolean>(false);\n\n readonly endStickyPreference = signal<'closed' | null>('closed');\n readonly endTempOpen = signal<boolean>(false);\n\n /**\n * Transitions are opt-in: only enabled after a user action (toggle, open,\n * close, dismiss). Viewport changes clear this flag so layout shifts are\n * always instant — no flash of the aside sliding in/out on resize.\n */\n readonly animate = signal<boolean>(false);\n\n // ===== COMPUTED =====\n readonly sidenavOpen = computed(() => {\n if (this.stickyPreference() === 'closed') {\n return false;\n }\n\n if (this.tempOpen()) {\n return true;\n }\n\n return !this.isMobile();\n });\n\n readonly showOverlay = computed(() => {\n if (!this.sidenavOpen()) {\n return false;\n }\n\n if (this.mode() === 'over') {\n return true;\n }\n\n return this.isMobile();\n });\n\n readonly isCollapsed = computed(() => {\n if (this.mode() !== 'icon') {\n return false;\n }\n\n if (this.sidenavOpen()) {\n return false;\n }\n\n if (this.isMobile()) {\n return this.collapseOnMobile();\n }\n\n return true;\n });\n\n readonly endOpen = computed(() => {\n if (this.endStickyPreference() === 'closed') {\n return false;\n }\n\n if (this.endTempOpen()) {\n return true;\n }\n\n return !this.isMobile();\n });\n\n readonly showEndOverlay = computed(() => {\n if (!this.endOpen()) {\n return false;\n }\n\n if (this.endMode() === 'over') {\n return true;\n }\n\n return this.isMobile();\n });\n\n readonly isEndCollapsed = computed(() => {\n return this.endMode() === 'icon' && !this.endOpen() && !this.isMobile();\n });\n\n readonly classes = computed(() => {\n const mode = this.mode();\n\n const endMode = this.endMode();\n\n return [\n 'fk-app-shell',\n `fk-app-shell--${mode}`,\n this.sticky() ? 'fk-app-shell--sticky' : '',\n this.sidenavOpen() ? 'fk-app-shell--open' : '',\n this.isCollapsed() ? 'fk-app-shell--collapsed' : '',\n this.isMobile() ? 'fk-app-shell--mobile' : '',\n this.animate() ? 'fk-app-shell--animate' : '',\n `fk-app-shell--end-${endMode}`,\n this.endOpen() ? 'fk-app-shell--end-open' : '',\n this.isEndCollapsed() ? 'fk-app-shell--end-collapsed' : '',\n ]\n .filter(Boolean)\n .join(' ');\n });\n\n @HostBinding('style')\n get hostStyle() {\n return {\n '--fk-app-shell-sidenav-width': this.sidenavWidth(),\n '--fk-app-shell-collapsed-width': this.collapsedWidth(),\n '--fk-app-shell-end-width': this.endWidth(),\n '--fk-app-shell-end-collapsed-width': this.endCollapsedWidth(),\n };\n }\n\n // ===== BREAKPOINT =====\n private readonly breakpointObserver = inject(BreakpointObserver);\n private readonly destroyRef = inject(DestroyRef);\n\n constructor() {\n // Defer the breakpoint subscription until the first client-side render.\n // During SSR, CDK's MediaMatcher returns `{ matches: false }` for every\n // query, which would flip `isMobile` to true and produce a mobile-layout\n // HTML in the server output, causing a visible flash of the overlay\n // sidebar before the client corrects it on hydration. `afterNextRender`\n // only runs in the browser, so `isMobile` stays at its `false` default\n // through SSR and the real viewport check happens post-hydration.\n afterNextRender(() => {\n this.breakpointObserver\n .observe(this.breakpoint())\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe((result) => {\n this.animate.set(false);\n this.isMobile.set(!result.matches);\n this.tempOpen.set(false);\n this.endTempOpen.set(false);\n });\n });\n }\n\n // ===== METHODS =====\n /** Open the primary sidenav, registering it with the overlay orchestrator on mobile. */\n openSidenav(): void {\n this.animate.set(true);\n this.stickyPreference.set(null);\n\n if (this.isMobile()) {\n this.tempOpen.set(true);\n\n this.navOverlayId = this.orchestrator.register({\n priority: OVERLAY_PRIORITY.NAVIGATION,\n type: 'navigation',\n close: () => this.closeSidenav(),\n });\n }\n }\n\n /** Close the primary sidenav and unregister it from the overlay orchestrator. */\n closeSidenav(): void {\n this.animate.set(true);\n this.stickyPreference.set('closed');\n this.tempOpen.set(false);\n\n if (this.navOverlayId) {\n this.orchestrator.unregister(this.navOverlayId);\n this.navOverlayId = null;\n }\n }\n\n /** Toggle the primary sidenav between open and closed. */\n toggleSidenav(): void {\n if (this.sidenavOpen()) {\n this.closeSidenav();\n } else {\n this.openSidenav();\n }\n }\n\n /** Dismiss the sidenav overlay on mobile without marking it as permanently closed. */\n dismissSidenav(): void {\n this.animate.set(true);\n this.tempOpen.set(false);\n }\n\n /** Open the end panel, registering it with the overlay orchestrator on mobile. */\n openEnd(): void {\n this.animate.set(true);\n this.endStickyPreference.set(null);\n\n if (this.isMobile()) {\n this.endTempOpen.set(true);\n\n this.endOverlayId = this.orchestrator.register({\n priority: OVERLAY_PRIORITY.NAVIGATION,\n type: 'navigation',\n close: () => this.closeEnd(),\n });\n }\n }\n\n /** Close the end panel and unregister it from the overlay orchestrator. */\n closeEnd(): void {\n this.animate.set(true);\n this.endStickyPreference.set('closed');\n this.endTempOpen.set(false);\n\n if (this.endOverlayId) {\n this.orchestrator.unregister(this.endOverlayId);\n this.endOverlayId = null;\n }\n }\n\n /** Toggle the end panel between open and closed. */\n toggleEnd(): void {\n if (this.endOpen()) {\n this.closeEnd();\n } else {\n this.openEnd();\n }\n }\n\n /** Dismiss the end panel overlay and unregister it from the overlay orchestrator. */\n dismissEnd(): void {\n this.animate.set(true);\n this.endStickyPreference.set('closed');\n this.endTempOpen.set(false);\n\n if (this.endOverlayId) {\n this.orchestrator.unregister(this.endOverlayId);\n this.endOverlayId = null;\n }\n }\n}\n","<div [class]=\"classes()\">\n <header class=\"fk-app-shell__header\">\n <ng-content select=\"[appShellHeader]\" />\n </header>\n\n <div class=\"fk-app-shell__body\">\n @if (showOverlay()) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div class=\"fk-app-shell__overlay\" (click)=\"dismissSidenav()\"></div>\n }\n\n <aside\n class=\"fk-app-shell__aside\"\n [class.fk-app-shell__aside--open]=\"sidenavOpen()\"\n [class.fk-app-shell__aside--collapsed]=\"isCollapsed()\"\n >\n <ng-content select=\"[appShellSidenav]\" />\n </aside>\n\n <main class=\"fk-app-shell__content\">\n <ng-content select=\"[appShellContent]\" />\n </main>\n\n @if (showEndOverlay()) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div class=\"fk-app-shell__end-overlay\" (click)=\"dismissEnd()\"></div>\n }\n\n <aside\n class=\"fk-app-shell__end\"\n [class.fk-app-shell__end--open]=\"endOpen()\"\n [class.fk-app-shell__end--collapsed]=\"isEndCollapsed()\"\n >\n <ng-content select=\"[appShellEnd]\" />\n </aside>\n </div>\n\n <footer class=\"fk-app-shell__footer\">\n <ng-content select=\"[appShellFooter]\" />\n </footer>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,132 @@
1
+ import { isPlatformBrowser } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, ElementRef, NgZone, DestroyRef, PLATFORM_ID, input, model, signal, computed, HostBinding, ChangeDetectionStrategy, Component } from '@angular/core';
4
+
5
+ class FkContentSplitComponent {
6
+ el = inject((ElementRef));
7
+ ngZone = inject(NgZone);
8
+ destroyRef = inject(DestroyRef);
9
+ platformId = inject(PLATFORM_ID);
10
+ // ===== BASE PROPS =====
11
+ className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
12
+ // ===== INPUTS =====
13
+ /** Width of the left panel when the layout is in wide mode. */
14
+ leftWidth = input('280px', ...(ngDevMode ? [{ debugName: "leftWidth" }] : /* istanbul ignore next */ []));
15
+ /** Width of the left panel when collapsed to a narrow icon rail. */
16
+ railWidth = input('64px', ...(ngDevMode ? [{ debugName: "railWidth" }] : /* istanbul ignore next */ []));
17
+ /** Host element pixel width below which the layout switches to narrow mode. */
18
+ breakpoint = input(700, ...(ngDevMode ? [{ debugName: "breakpoint" }] : /* istanbul ignore next */ []));
19
+ /** Two-way binding for the collapsed state of the left panel. */
20
+ collapsed = model(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
21
+ // ===== INTERNAL STATE =====
22
+ layout = signal('wide', ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
23
+ animate = signal(false, ...(ngDevMode ? [{ debugName: "animate" }] : /* istanbul ignore next */ []));
24
+ observer = null;
25
+ // ===== COMPUTED =====
26
+ isNarrow = computed(() => this.layout() === 'narrow', ...(ngDevMode ? [{ debugName: "isNarrow" }] : /* istanbul ignore next */ []));
27
+ isWide = computed(() => this.layout() === 'wide', ...(ngDevMode ? [{ debugName: "isWide" }] : /* istanbul ignore next */ []));
28
+ showOverlay = computed(() => this.isNarrow() && !this.collapsed(), ...(ngDevMode ? [{ debugName: "showOverlay" }] : /* istanbul ignore next */ []));
29
+ classes = computed(() => [
30
+ 'fk-content-split',
31
+ this.isNarrow() ? 'fk-content-split--narrow' : 'fk-content-split--wide',
32
+ this.isNarrow() && !this.collapsed() ? 'fk-content-split--open' : '',
33
+ this.isNarrow() && this.collapsed() ? 'fk-content-split--closed' : '',
34
+ this.animate() ? 'fk-content-split--animate' : '',
35
+ this.className(),
36
+ ]
37
+ .filter(Boolean)
38
+ .join(' '), ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
39
+ // ===== HOST BINDINGS =====
40
+ get hostClass() {
41
+ return this.classes();
42
+ }
43
+ get hostLeftWidth() {
44
+ return this.leftWidth();
45
+ }
46
+ get hostRailWidth() {
47
+ return this.railWidth();
48
+ }
49
+ // ===== CONSTRUCTOR =====
50
+ constructor() {
51
+ // ResizeObserver is browser-only — the SSR pass has no live
52
+ // layout to observe, so the initial layout signal stays at its
53
+ // default ("wide") and the browser pass wires the observer once
54
+ // hydration mounts the element.
55
+ if (isPlatformBrowser(this.platformId)) {
56
+ this.ngZone.runOutsideAngular(() => {
57
+ this.observer = new ResizeObserver((entries) => {
58
+ const entry = entries[0];
59
+ if (!entry) {
60
+ return;
61
+ }
62
+ const hostWidth = entry.contentRect.width;
63
+ const bp = this.breakpoint();
64
+ if (hostWidth < bp && this.layout() !== 'narrow') {
65
+ this.ngZone.run(() => {
66
+ this.layout.set('narrow');
67
+ this.animate.set(false);
68
+ this.collapsed.set(true);
69
+ });
70
+ }
71
+ else if (hostWidth >= bp && this.layout() !== 'wide') {
72
+ this.ngZone.run(() => {
73
+ this.layout.set('wide');
74
+ this.animate.set(false);
75
+ this.collapsed.set(true);
76
+ });
77
+ }
78
+ });
79
+ this.observer.observe(this.el.nativeElement);
80
+ });
81
+ }
82
+ this.destroyRef.onDestroy(() => {
83
+ this.observer?.disconnect();
84
+ });
85
+ }
86
+ // ===== PUBLIC METHODS =====
87
+ /** Expand the left panel. */
88
+ open() {
89
+ this.animate.set(true);
90
+ this.collapsed.set(false);
91
+ }
92
+ /** Collapse the left panel. */
93
+ close() {
94
+ this.animate.set(true);
95
+ this.collapsed.set(true);
96
+ }
97
+ /** Toggle the left panel between expanded and collapsed. */
98
+ toggle() {
99
+ if (this.collapsed()) {
100
+ this.open();
101
+ }
102
+ else {
103
+ this.close();
104
+ }
105
+ }
106
+ /** Dismiss the left panel overlay on narrow viewports (alias for `close`). */
107
+ dismiss() {
108
+ this.close();
109
+ }
110
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FkContentSplitComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
111
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: FkContentSplitComponent, isStandalone: true, selector: "fk-content-split", inputs: { className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, leftWidth: { classPropertyName: "leftWidth", publicName: "leftWidth", isSignal: true, isRequired: false, transformFunction: null }, railWidth: { classPropertyName: "railWidth", publicName: "railWidth", isSignal: true, isRequired: false, transformFunction: null }, breakpoint: { classPropertyName: "breakpoint", publicName: "breakpoint", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange" }, host: { properties: { "class": "this.hostClass", "style.--fk-content-split-left-width": "this.hostLeftWidth", "style.--fk-content-split-rail-width": "this.hostRailWidth" } }, ngImport: i0, template: "@if (showOverlay()) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div class=\"fk-content-split__overlay\" (click)=\"dismiss()\"></div>\n}\n\n<div\n class=\"fk-content-split__left\"\n [class.fk-content-split__left--open]=\"!collapsed()\"\n>\n <div class=\"fk-content-split__left-content\">\n <ng-content select=\"[contentSplitLeft]\" />\n </div>\n</div>\n\n<div class=\"fk-content-split__rail\">\n <button\n class=\"fk-content-split__rail-toggle\"\n type=\"button\"\n [attr.aria-label]=\"collapsed() ? 'Open panel' : 'Close panel'\"\n (click)=\"toggle()\"\n >\n <span\n class=\"fk-content-split__rail-toggle-icon\"\n [class.fk-content-split__rail-toggle-icon--open]=\"!collapsed()\"\n aria-hidden=\"true\"\n >\n <span class=\"fk-content-split__rail-toggle-projected\">\n <ng-content select=\"[contentSplitToggleIcon]\" />\n </span>\n <svg\n class=\"fk-content-split__rail-toggle-fallback\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"M15.25 9H2.75\" />\n <path d=\"M11 4.75L15.25 9L11 13.25\" />\n </svg>\n </span>\n </button>\n</div>\n\n<div class=\"fk-content-split__right\">\n <ng-content select=\"[contentSplitRight]\" />\n</div>\n", styles: [":host{display:flex;position:relative;height:100%;overflow:hidden}.fk-content-split__left{width:var(--fk-content-split-left-width, 280px);flex-shrink:0;overflow:hidden;background:var(--fk-content-split-left-bg, var(--fk-color-surface, #ffffff));border-right:var(--fk-content-split-left-border, 1px solid var(--fk-color-border, #e2e8f0))}.fk-content-split__left-content{height:100%;overflow-y:auto;overscroll-behavior:contain}.fk-content-split__overlay,.fk-content-split__rail{display:none}.fk-content-split__right{flex:1;min-width:0;overflow-y:auto}:host(.fk-content-split--narrow) .fk-content-split__left{position:absolute;top:0;left:0;bottom:0;z-index:var(--fk-content-split-panel-z-index, 11);width:min(var(--fk-content-split-left-width, 280px),100% - var(--fk-content-split-rail-width, 64px));transform:translate(-100%);visibility:hidden;pointer-events:none;background:var(--fk-content-split-left-bg, var(--fk-color-surface, #ffffff));border-right:var(--fk-content-split-left-border, 1px solid var(--fk-color-border, #e2e8f0))}:host(.fk-content-split--narrow) .fk-content-split__left--open{transform:translate(var(--fk-content-split-rail-width, 64px));visibility:visible;pointer-events:auto}:host(.fk-content-split--narrow) .fk-content-split__overlay{display:block;position:absolute;inset:0;left:var(--fk-content-split-rail-width, 64px);z-index:var(--fk-content-split-overlay-z-index, 10);background:var(--fk-content-split-overlay-bg, var(--fk-black-50, rgba(0, 0, 0, .5)))}:host(.fk-content-split--narrow) .fk-content-split__rail{display:flex;flex-direction:column;align-items:center;flex-shrink:0;width:var(--fk-content-split-rail-width, 64px);background:var(--fk-content-split-left-bg, var(--fk-color-surface, #ffffff));border-right:var(--fk-content-split-left-border, 1px solid var(--fk-color-border, #e2e8f0));overflow-y:auto;overscroll-behavior:contain}:host(.fk-content-split--narrow) .fk-content-split__rail-toggle{display:flex;align-items:center;justify-content:center;width:100%;padding:var(--fk-content-split-rail-toggle-padding, var(--fk-rhythm-3, .75rem)) 0;border:none;background:transparent;color:var(--fk-content-split-rail-toggle-color, var(--fk-color-muted, #64748b));cursor:pointer;flex-shrink:0}:host(.fk-content-split--narrow) .fk-content-split__rail-toggle:hover{color:var(--fk-content-split-rail-toggle-color-hover, var(--fk-color-text, #1f2d3d));background:var(--fk-content-split-rail-toggle-bg-hover, var(--fk-color-surface-muted, #f1f5f9))}:host(.fk-content-split--narrow) .fk-content-split__rail-toggle:focus-visible{outline:var(--fk-focus-ring, 0 0 0 3px rgba(59, 130, 246, .5));outline-offset:-2px}:host(.fk-content-split--narrow) .fk-content-split__rail-toggle-icon{display:flex;align-items:center;justify-content:center;transition:transform .15s ease}:host(.fk-content-split--narrow) .fk-content-split__rail-toggle-icon--open{transform:rotate(180deg)}:host(.fk-content-split--narrow) .fk-content-split__rail-toggle-projected{display:contents}:host(.fk-content-split--narrow) .fk-content-split__rail-toggle-projected:empty+.fk-content-split__rail-toggle-fallback{display:block}:host(.fk-content-split--narrow) .fk-content-split__rail-toggle-fallback{display:none}:host(.fk-content-split--narrow) .fk-content-split__right{flex:1;min-width:0}:host(.fk-content-split--animate) .fk-content-split__left{transition:transform .25s cubic-bezier(.4,0,.2,1),visibility 0s .25s}:host(.fk-content-split--animate.fk-content-split--open) .fk-content-split__left{transition:transform .25s cubic-bezier(.4,0,.2,1),visibility 0s 0s}@media(prefers-reduced-motion:reduce){:host(.fk-content-split--animate) .fk-content-split__left{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
112
+ }
113
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FkContentSplitComponent, decorators: [{
114
+ type: Component,
115
+ args: [{ selector: 'fk-content-split', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (showOverlay()) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div class=\"fk-content-split__overlay\" (click)=\"dismiss()\"></div>\n}\n\n<div\n class=\"fk-content-split__left\"\n [class.fk-content-split__left--open]=\"!collapsed()\"\n>\n <div class=\"fk-content-split__left-content\">\n <ng-content select=\"[contentSplitLeft]\" />\n </div>\n</div>\n\n<div class=\"fk-content-split__rail\">\n <button\n class=\"fk-content-split__rail-toggle\"\n type=\"button\"\n [attr.aria-label]=\"collapsed() ? 'Open panel' : 'Close panel'\"\n (click)=\"toggle()\"\n >\n <span\n class=\"fk-content-split__rail-toggle-icon\"\n [class.fk-content-split__rail-toggle-icon--open]=\"!collapsed()\"\n aria-hidden=\"true\"\n >\n <span class=\"fk-content-split__rail-toggle-projected\">\n <ng-content select=\"[contentSplitToggleIcon]\" />\n </span>\n <svg\n class=\"fk-content-split__rail-toggle-fallback\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"M15.25 9H2.75\" />\n <path d=\"M11 4.75L15.25 9L11 13.25\" />\n </svg>\n </span>\n </button>\n</div>\n\n<div class=\"fk-content-split__right\">\n <ng-content select=\"[contentSplitRight]\" />\n</div>\n", styles: [":host{display:flex;position:relative;height:100%;overflow:hidden}.fk-content-split__left{width:var(--fk-content-split-left-width, 280px);flex-shrink:0;overflow:hidden;background:var(--fk-content-split-left-bg, var(--fk-color-surface, #ffffff));border-right:var(--fk-content-split-left-border, 1px solid var(--fk-color-border, #e2e8f0))}.fk-content-split__left-content{height:100%;overflow-y:auto;overscroll-behavior:contain}.fk-content-split__overlay,.fk-content-split__rail{display:none}.fk-content-split__right{flex:1;min-width:0;overflow-y:auto}:host(.fk-content-split--narrow) .fk-content-split__left{position:absolute;top:0;left:0;bottom:0;z-index:var(--fk-content-split-panel-z-index, 11);width:min(var(--fk-content-split-left-width, 280px),100% - var(--fk-content-split-rail-width, 64px));transform:translate(-100%);visibility:hidden;pointer-events:none;background:var(--fk-content-split-left-bg, var(--fk-color-surface, #ffffff));border-right:var(--fk-content-split-left-border, 1px solid var(--fk-color-border, #e2e8f0))}:host(.fk-content-split--narrow) .fk-content-split__left--open{transform:translate(var(--fk-content-split-rail-width, 64px));visibility:visible;pointer-events:auto}:host(.fk-content-split--narrow) .fk-content-split__overlay{display:block;position:absolute;inset:0;left:var(--fk-content-split-rail-width, 64px);z-index:var(--fk-content-split-overlay-z-index, 10);background:var(--fk-content-split-overlay-bg, var(--fk-black-50, rgba(0, 0, 0, .5)))}:host(.fk-content-split--narrow) .fk-content-split__rail{display:flex;flex-direction:column;align-items:center;flex-shrink:0;width:var(--fk-content-split-rail-width, 64px);background:var(--fk-content-split-left-bg, var(--fk-color-surface, #ffffff));border-right:var(--fk-content-split-left-border, 1px solid var(--fk-color-border, #e2e8f0));overflow-y:auto;overscroll-behavior:contain}:host(.fk-content-split--narrow) .fk-content-split__rail-toggle{display:flex;align-items:center;justify-content:center;width:100%;padding:var(--fk-content-split-rail-toggle-padding, var(--fk-rhythm-3, .75rem)) 0;border:none;background:transparent;color:var(--fk-content-split-rail-toggle-color, var(--fk-color-muted, #64748b));cursor:pointer;flex-shrink:0}:host(.fk-content-split--narrow) .fk-content-split__rail-toggle:hover{color:var(--fk-content-split-rail-toggle-color-hover, var(--fk-color-text, #1f2d3d));background:var(--fk-content-split-rail-toggle-bg-hover, var(--fk-color-surface-muted, #f1f5f9))}:host(.fk-content-split--narrow) .fk-content-split__rail-toggle:focus-visible{outline:var(--fk-focus-ring, 0 0 0 3px rgba(59, 130, 246, .5));outline-offset:-2px}:host(.fk-content-split--narrow) .fk-content-split__rail-toggle-icon{display:flex;align-items:center;justify-content:center;transition:transform .15s ease}:host(.fk-content-split--narrow) .fk-content-split__rail-toggle-icon--open{transform:rotate(180deg)}:host(.fk-content-split--narrow) .fk-content-split__rail-toggle-projected{display:contents}:host(.fk-content-split--narrow) .fk-content-split__rail-toggle-projected:empty+.fk-content-split__rail-toggle-fallback{display:block}:host(.fk-content-split--narrow) .fk-content-split__rail-toggle-fallback{display:none}:host(.fk-content-split--narrow) .fk-content-split__right{flex:1;min-width:0}:host(.fk-content-split--animate) .fk-content-split__left{transition:transform .25s cubic-bezier(.4,0,.2,1),visibility 0s .25s}:host(.fk-content-split--animate.fk-content-split--open) .fk-content-split__left{transition:transform .25s cubic-bezier(.4,0,.2,1),visibility 0s 0s}@media(prefers-reduced-motion:reduce){:host(.fk-content-split--animate) .fk-content-split__left{transition:none}}\n"] }]
116
+ }], ctorParameters: () => [], propDecorators: { className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], leftWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "leftWidth", required: false }] }], railWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "railWidth", required: false }] }], breakpoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "breakpoint", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], hostClass: [{
117
+ type: HostBinding,
118
+ args: ['class']
119
+ }], hostLeftWidth: [{
120
+ type: HostBinding,
121
+ args: ['style.--fk-content-split-left-width']
122
+ }], hostRailWidth: [{
123
+ type: HostBinding,
124
+ args: ['style.--fk-content-split-rail-width']
125
+ }] } });
126
+
127
+ /**
128
+ * Generated bundle index. Do not edit.
129
+ */
130
+
131
+ export { FkContentSplitComponent };
132
+ //# sourceMappingURL=frame-kit-ui-ng-layouts-content-split.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-kit-ui-ng-layouts-content-split.mjs","sources":["../../../../packages/ui-ng/layouts/content-split/content-split.component.ts","../../../../packages/ui-ng/layouts/content-split/content-split.component.html","../../../../packages/ui-ng/layouts/content-split/frame-kit-ui-ng-layouts-content-split.ts"],"sourcesContent":["import { isPlatformBrowser } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n ElementRef,\n HostBinding,\n inject,\n input,\n model,\n NgZone,\n PLATFORM_ID,\n signal,\n} from '@angular/core';\n\nimport type { ContentSplitLayout } from './content-split.types';\n\n@Component({\n selector: 'fk-content-split',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './content-split.component.html',\n styleUrl: './content-split.component.scss',\n})\nexport class FkContentSplitComponent {\n private readonly el = inject(ElementRef<HTMLElement>);\n private readonly ngZone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n private readonly platformId = inject(PLATFORM_ID);\n\n // ===== BASE PROPS =====\n readonly className = input<string>('');\n\n // ===== INPUTS =====\n /** Width of the left panel when the layout is in wide mode. */\n readonly leftWidth = input<string>('280px');\n /** Width of the left panel when collapsed to a narrow icon rail. */\n readonly railWidth = input<string>('64px');\n /** Host element pixel width below which the layout switches to narrow mode. */\n readonly breakpoint = input<number>(700);\n /** Two-way binding for the collapsed state of the left panel. */\n readonly collapsed = model<boolean>(false);\n\n // ===== INTERNAL STATE =====\n readonly layout = signal<ContentSplitLayout>('wide');\n readonly animate = signal(false);\n\n private observer: ResizeObserver | null = null;\n\n // ===== COMPUTED =====\n readonly isNarrow = computed(() => this.layout() === 'narrow');\n readonly isWide = computed(() => this.layout() === 'wide');\n readonly showOverlay = computed(() => this.isNarrow() && !this.collapsed());\n\n readonly classes = computed(() =>\n [\n 'fk-content-split',\n this.isNarrow() ? 'fk-content-split--narrow' : 'fk-content-split--wide',\n this.isNarrow() && !this.collapsed() ? 'fk-content-split--open' : '',\n this.isNarrow() && this.collapsed() ? 'fk-content-split--closed' : '',\n this.animate() ? 'fk-content-split--animate' : '',\n this.className(),\n ]\n .filter(Boolean)\n .join(' '),\n );\n\n // ===== HOST BINDINGS =====\n\n @HostBinding('class')\n get hostClass() {\n return this.classes();\n }\n\n @HostBinding('style.--fk-content-split-left-width')\n get hostLeftWidth() {\n return this.leftWidth();\n }\n\n @HostBinding('style.--fk-content-split-rail-width')\n get hostRailWidth() {\n return this.railWidth();\n }\n\n // ===== CONSTRUCTOR =====\n\n constructor() {\n // ResizeObserver is browser-only — the SSR pass has no live\n // layout to observe, so the initial layout signal stays at its\n // default (\"wide\") and the browser pass wires the observer once\n // hydration mounts the element.\n if (isPlatformBrowser(this.platformId)) {\n this.ngZone.runOutsideAngular(() => {\n this.observer = new ResizeObserver((entries) => {\n const entry = entries[0];\n\n if (!entry) {\n return;\n }\n\n const hostWidth = entry.contentRect.width;\n const bp = this.breakpoint();\n\n if (hostWidth < bp && this.layout() !== 'narrow') {\n this.ngZone.run(() => {\n this.layout.set('narrow');\n this.animate.set(false);\n this.collapsed.set(true);\n });\n } else if (hostWidth >= bp && this.layout() !== 'wide') {\n this.ngZone.run(() => {\n this.layout.set('wide');\n this.animate.set(false);\n this.collapsed.set(true);\n });\n }\n });\n\n this.observer.observe(this.el.nativeElement);\n });\n }\n\n this.destroyRef.onDestroy(() => {\n this.observer?.disconnect();\n });\n }\n\n // ===== PUBLIC METHODS =====\n\n /** Expand the left panel. */\n open(): void {\n this.animate.set(true);\n this.collapsed.set(false);\n }\n\n /** Collapse the left panel. */\n close(): void {\n this.animate.set(true);\n this.collapsed.set(true);\n }\n\n /** Toggle the left panel between expanded and collapsed. */\n toggle(): void {\n if (this.collapsed()) {\n this.open();\n } else {\n this.close();\n }\n }\n\n /** Dismiss the left panel overlay on narrow viewports (alias for `close`). */\n dismiss(): void {\n this.close();\n }\n}\n","@if (showOverlay()) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div class=\"fk-content-split__overlay\" (click)=\"dismiss()\"></div>\n}\n\n<div\n class=\"fk-content-split__left\"\n [class.fk-content-split__left--open]=\"!collapsed()\"\n>\n <div class=\"fk-content-split__left-content\">\n <ng-content select=\"[contentSplitLeft]\" />\n </div>\n</div>\n\n<div class=\"fk-content-split__rail\">\n <button\n class=\"fk-content-split__rail-toggle\"\n type=\"button\"\n [attr.aria-label]=\"collapsed() ? 'Open panel' : 'Close panel'\"\n (click)=\"toggle()\"\n >\n <span\n class=\"fk-content-split__rail-toggle-icon\"\n [class.fk-content-split__rail-toggle-icon--open]=\"!collapsed()\"\n aria-hidden=\"true\"\n >\n <span class=\"fk-content-split__rail-toggle-projected\">\n <ng-content select=\"[contentSplitToggleIcon]\" />\n </span>\n <svg\n class=\"fk-content-split__rail-toggle-fallback\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"M15.25 9H2.75\" />\n <path d=\"M11 4.75L15.25 9L11 13.25\" />\n </svg>\n </span>\n </button>\n</div>\n\n<div class=\"fk-content-split__right\">\n <ng-content select=\"[contentSplitRight]\" />\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,133 @@
1
+ import * as i0 from '@angular/core';
2
+ import { signal, computed, Injectable } from '@angular/core';
3
+
4
+ let nextOverlayId = 0;
5
+ /**
6
+ * Centralized overlay orchestrator.
7
+ *
8
+ * Tracks all open overlays (dialogs, drawers, popovers, navigation, tooltips)
9
+ * in a priority-based stack. When a new overlay opens:
10
+ *
11
+ * - All overlays with LOWER priority are closed automatically.
12
+ * - Overlays with EQUAL or HIGHER priority are left open.
13
+ *
14
+ * This prevents UI conflicts like drawers staying open behind dialogs,
15
+ * or kebab menus persisting when a modal pops up.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const id = orchestrator.register({
20
+ * priority: OVERLAY_PRIORITY.DRAWER,
21
+ * type: 'drawer',
22
+ * close: () => drawerRef.close(),
23
+ * });
24
+ *
25
+ * // Later, when the overlay closes:
26
+ * orchestrator.unregister(id);
27
+ * ```
28
+ */
29
+ class OverlayOrchestrator {
30
+ entries = signal([], ...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
31
+ /** Whether any overlay is currently open. */
32
+ hasActiveOverlay = computed(() => this.entries().length > 0, ...(ngDevMode ? [{ debugName: "hasActiveOverlay" }] : /* istanbul ignore next */ []));
33
+ /** The topmost (highest priority) overlay. */
34
+ topOverlay = computed(() => {
35
+ const all = this.entries();
36
+ if (all.length === 0) {
37
+ return null;
38
+ }
39
+ return all.reduce((top, entry) => entry.priority >= top.priority ? entry : top);
40
+ }, ...(ngDevMode ? [{ debugName: "topOverlay" }] : /* istanbul ignore next */ []));
41
+ /** Current number of active overlays. */
42
+ count = computed(() => this.entries().length, ...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
43
+ /**
44
+ * Register an overlay and close all overlays with lower priority.
45
+ *
46
+ * @returns The unique overlay ID — pass this to `unregister()` when closing.
47
+ */
48
+ register(entry) {
49
+ const id = `overlay-${nextOverlayId++}`;
50
+ // Close all overlays with strictly lower priority
51
+ const current = this.entries();
52
+ const toClose = current.filter((e) => e.priority < entry.priority);
53
+ for (const overlay of toClose) {
54
+ overlay.close();
55
+ }
56
+ // Remove closed overlays and add the new one
57
+ const remaining = current.filter((e) => e.priority >= entry.priority);
58
+ this.entries.set([...remaining, { ...entry, id }]);
59
+ return id;
60
+ }
61
+ /**
62
+ * Unregister an overlay by ID. Call this when the overlay closes.
63
+ */
64
+ unregister(id) {
65
+ this.entries.update((entries) => entries.filter((e) => e.id !== id));
66
+ }
67
+ /**
68
+ * Check if an overlay with the given priority or higher is currently open.
69
+ * Useful for guards that want to prevent opening lower-priority overlays.
70
+ */
71
+ hasOverlayAtOrAbove(priority) {
72
+ return this.entries().some((e) => e.priority >= priority);
73
+ }
74
+ /**
75
+ * Check if any overlay of a specific type is open.
76
+ */
77
+ hasOverlayOfType(type) {
78
+ return this.entries().some((e) => e.type === type);
79
+ }
80
+ /**
81
+ * Close all overlays. Closes from lowest to highest priority.
82
+ */
83
+ closeAll() {
84
+ const sorted = [...this.entries()].sort((a, b) => a.priority - b.priority);
85
+ for (const entry of sorted) {
86
+ entry.close();
87
+ }
88
+ this.entries.set([]);
89
+ }
90
+ /**
91
+ * Close all overlays at or below the given priority.
92
+ */
93
+ closeAtOrBelow(priority) {
94
+ const current = this.entries();
95
+ const toClose = current.filter((e) => e.priority <= priority);
96
+ for (const overlay of toClose) {
97
+ overlay.close();
98
+ }
99
+ this.entries.update((entries) => entries.filter((e) => e.priority > priority));
100
+ }
101
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OverlayOrchestrator, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
102
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OverlayOrchestrator, providedIn: 'root' });
103
+ }
104
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OverlayOrchestrator, decorators: [{
105
+ type: Injectable,
106
+ args: [{ providedIn: 'root' }]
107
+ }] });
108
+
109
+ /**
110
+ * Default priority levels for common overlay types.
111
+ * Higher number = higher priority = stays open when lower-priority overlays open.
112
+ *
113
+ * Consumers can use any number — these are conventions, not constraints.
114
+ */
115
+ const OVERLAY_PRIORITY = {
116
+ /** Tooltips, inline popovers */
117
+ TOOLTIP: 0,
118
+ /** Dropdown menus, kebab menus, popovers */
119
+ POPOVER: 10,
120
+ /** Side panels, detail drawers */
121
+ DRAWER: 20,
122
+ /** Modal dialogs, confirmations */
123
+ DIALOG: 30,
124
+ /** App-level navigation (sidebar, mobile nav) */
125
+ NAVIGATION: 40,
126
+ };
127
+
128
+ /**
129
+ * Generated bundle index. Do not edit.
130
+ */
131
+
132
+ export { OVERLAY_PRIORITY, OverlayOrchestrator };
133
+ //# sourceMappingURL=frame-kit-ui-ng-services-overlay-orchestrator.mjs.map