@brickclay-org/ui 0.1.76 → 0.1.77

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.
package/index.d.ts CHANGED
@@ -969,9 +969,9 @@ interface Rgb {
969
969
  /** The neutral look used when there is no colour (or an unparseable one). */
970
970
  declare const NEUTRAL_APPEARANCE: ColorAppearance;
971
971
  /**
972
- * Parses hex and rgb()/rgba() colours. Named CSS colours ("red") are not
973
- * supported — they'd need a live DOM to resolve — and return null, which makes
974
- * the caller fall back to the neutral (uncoloured) appearance.
972
+ * Parses hex, rgb()/rgba() and hsl()/hsla() colours. Named CSS colours ("red")
973
+ * are not supported — they'd need a live DOM to resolve — and return null, which
974
+ * makes the caller fall back to the neutral (uncoloured) appearance.
975
975
  */
976
976
  declare function parseColor(input: string | null | undefined): Rgb | null;
977
977
  /**
@@ -1035,7 +1035,7 @@ declare class BkSelect implements ControlValueAccessor, AfterViewInit, OnDestroy
1035
1035
  disabled: _angular_core.ModelSignal<boolean>;
1036
1036
  loading: _angular_core.InputSignal<boolean>;
1037
1037
  closeOnSelect: _angular_core.InputSignal<boolean>;
1038
- dropdownPosition: _angular_core.InputSignal<"top" | "bottom">;
1038
+ dropdownPosition: _angular_core.InputSignal<"bottom" | "top">;
1039
1039
  hasError: boolean;
1040
1040
  errorMessage: string;
1041
1041
  appendToBody: _angular_core.InputSignal<boolean>;
@@ -1064,7 +1064,7 @@ declare class BkSelect implements ControlValueAccessor, AfterViewInit, OnDestroy
1064
1064
  selectedOptions: _angular_core.WritableSignal<any[]>;
1065
1065
  searchTerm: _angular_core.WritableSignal<string>;
1066
1066
  markedIndex: _angular_core.WritableSignal<number>;
1067
- placement: _angular_core.WritableSignal<"top" | "bottom">;
1067
+ placement: _angular_core.WritableSignal<"bottom" | "top">;
1068
1068
  private originalPanelParent;
1069
1069
  private originalPanelAnchor;
1070
1070
  private panelInBody;
@@ -2221,11 +2221,9 @@ declare class BkHierarchicalSelect implements ControlValueAccessor, AfterViewIni
2221
2221
  /** When true, dropdown is positioned fixed and sized to the trigger (use inside modals/popups). */
2222
2222
  appendToBody: _angular_core.InputSignal<boolean>;
2223
2223
  /** Open above or below the trigger (also used when appendToBody is true for fixed coordinates). */
2224
- dropdownPosition: _angular_core.InputSignal<"top" | "bottom">;
2224
+ dropdownPosition: _angular_core.InputSignal<"bottom" | "top">;
2225
2225
  /** Key for option color (e.g. "color"). When set, option label and selected value use this color. */
2226
2226
  colorKey: _angular_core.InputSignal<string>;
2227
- /** Whether to show clear button when a value is selected. */
2228
- clearable: _angular_core.InputSignal<boolean>;
2229
2227
  /**
2230
2228
  * Show an accent dot beside each option, driven by `colorKey`, and tint the
2231
2229
  * control with the selected node's colour (same treatment as bk-status-select).
@@ -2237,6 +2235,8 @@ declare class BkHierarchicalSelect implements ControlValueAccessor, AfterViewIni
2237
2235
  * trees render exactly as before.
2238
2236
  */
2239
2237
  inheritColor: _angular_core.InputSignal<boolean>;
2238
+ /** Whether to show clear button when a value is selected. */
2239
+ clearable: _angular_core.InputSignal<boolean>;
2240
2240
  /** Optional anchor key to scope dropdown to one subtree (e.g. module/category key). */
2241
2241
  restrictKey: _angular_core.InputSignal<any>;
2242
2242
  hasError: _angular_core.InputSignal<boolean>;
@@ -2250,7 +2250,7 @@ declare class BkHierarchicalSelect implements ControlValueAccessor, AfterViewIni
2250
2250
  controlWrapper: ElementRef<HTMLDivElement>;
2251
2251
  dropdownPanel?: ElementRef<HTMLDivElement>;
2252
2252
  private el;
2253
- placement: _angular_core.WritableSignal<"top" | "bottom">;
2253
+ placement: _angular_core.WritableSignal<"bottom" | "top">;
2254
2254
  private originalPanelParent;
2255
2255
  private originalPanelAnchor;
2256
2256
  private panelInBody;
@@ -2336,6 +2336,12 @@ declare class BkHierarchicalSelect implements ControlValueAccessor, AfterViewIni
2336
2336
  goBack(): void;
2337
2337
  onSearchInput(event: Event): void;
2338
2338
  selectItem(node: HierarchicalNode, event?: Event): void;
2339
+ /**
2340
+ * Select/deselect any node from its checkbox — parent, child or leaf, at any
2341
+ * depth. Stops the event so a parent row's own handler doesn't also navigate
2342
+ * into its children.
2343
+ */
2344
+ toggleParentSelection(node: HierarchicalNode, event?: Event): void;
2339
2345
  openFromLabel(event: MouseEvent): void;
2340
2346
  onClickOutside(e: Event): void;
2341
2347
  private _value;
@@ -2349,7 +2355,7 @@ declare class BkHierarchicalSelect implements ControlValueAccessor, AfterViewIni
2349
2355
  private findNodeByValue;
2350
2356
  handleClear(event: Event): void;
2351
2357
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<BkHierarchicalSelect, never>;
2352
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkHierarchicalSelect, "bk-hierarchical-select", never, { "items": { "alias": "items"; "required": false; "isSignal": true; }; "labelKey": { "alias": "labelKey"; "required": false; "isSignal": true; }; "valueKey": { "alias": "valueKey"; "required": false; "isSignal": true; }; "childrenKey": { "alias": "childrenKey"; "required": false; "isSignal": true; }; "clearTooltip": { "alias": "clearTooltip"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "iconSrc": { "alias": "iconSrc"; "required": false; "isSignal": true; }; "iconAlt": { "alias": "iconAlt"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "allowParentSelection": { "alias": "allowParentSelection"; "required": false; "isSignal": true; }; "backToMainText": { "alias": "backToMainText"; "required": false; "isSignal": true; }; "searchPlaceholder": { "alias": "searchPlaceholder"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "appendToBody": { "alias": "appendToBody"; "required": false; "isSignal": true; }; "dropdownPosition": { "alias": "dropdownPosition"; "required": false; "isSignal": true; }; "colorKey": { "alias": "colorKey"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "showDots": { "alias": "showDots"; "required": false; "isSignal": true; }; "inheritColor": { "alias": "inheritColor"; "required": false; "isSignal": true; }; "restrictKey": { "alias": "restrictKey"; "required": false; "isSignal": true; }; "hasError": { "alias": "hasError"; "required": false; "isSignal": true; }; }, { "selectionChange": "selectionChange"; "valueChange": "valueChange"; "clear": "clear"; }, never, never, true, never>;
2358
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkHierarchicalSelect, "bk-hierarchical-select", never, { "items": { "alias": "items"; "required": false; "isSignal": true; }; "labelKey": { "alias": "labelKey"; "required": false; "isSignal": true; }; "valueKey": { "alias": "valueKey"; "required": false; "isSignal": true; }; "childrenKey": { "alias": "childrenKey"; "required": false; "isSignal": true; }; "clearTooltip": { "alias": "clearTooltip"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "iconSrc": { "alias": "iconSrc"; "required": false; "isSignal": true; }; "iconAlt": { "alias": "iconAlt"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "allowParentSelection": { "alias": "allowParentSelection"; "required": false; "isSignal": true; }; "backToMainText": { "alias": "backToMainText"; "required": false; "isSignal": true; }; "searchPlaceholder": { "alias": "searchPlaceholder"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "appendToBody": { "alias": "appendToBody"; "required": false; "isSignal": true; }; "dropdownPosition": { "alias": "dropdownPosition"; "required": false; "isSignal": true; }; "colorKey": { "alias": "colorKey"; "required": false; "isSignal": true; }; "showDots": { "alias": "showDots"; "required": false; "isSignal": true; }; "inheritColor": { "alias": "inheritColor"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "restrictKey": { "alias": "restrictKey"; "required": false; "isSignal": true; }; "hasError": { "alias": "hasError"; "required": false; "isSignal": true; }; }, { "selectionChange": "selectionChange"; "valueChange": "valueChange"; "clear": "clear"; }, never, never, true, never>;
2353
2359
  }
2354
2360
 
2355
2361
  type ToastSeverity = 'success' | 'error' | 'warn' | 'info' | 'default';
@@ -2702,6 +2708,10 @@ declare class BkDropdown {
2702
2708
 
2703
2709
  type MenuOrientation = 'horizontal' | 'vertical';
2704
2710
  type MenuSubmenuMode = 'inline' | 'popup';
2711
+ type MenuTrigger = 'hover' | 'click';
2712
+ type MenuSize = 'default' | 'compact';
2713
+ /** Side a fly-out ended up on, once measured against the viewport. */
2714
+ type MenuPopupSide = 'right' | 'left' | 'down' | 'up';
2705
2715
  interface MenuItem {
2706
2716
  /** Unique id — matched against `activeItemId`. */
2707
2717
  id: string;
@@ -2713,6 +2723,8 @@ interface MenuItem {
2713
2723
  iconAlt?: string;
2714
2724
  /** Disables just this item (and blocks opening its submenu). */
2715
2725
  disabled?: boolean;
2726
+ /** Heading shown above this item's children, titling the group. */
2727
+ childrenLabel?: string;
2716
2728
  /** Nested items — presence of children turns this into an expandable node. */
2717
2729
  children?: MenuItem[];
2718
2730
  }
@@ -2723,15 +2735,24 @@ interface MenuItem {
2723
2735
  * - Orientation: `vertical` (stacked) / `horizontal` (menubar row).
2724
2736
  * - Items with `children` are expandable, shown with a chevron that rotates
2725
2737
  * open/closed.
2726
- * - Submenus render inline (accordion) or as pop-ups. Pop-ups adapt to the
2727
- * orientation: they drop *below* a horizontal top-level item and fly out to
2728
- * the *right* for vertical menus / deeper levels.
2738
+ * - Submenus render inline (accordion) or as pop-ups, opened by hover or click.
2739
+ * Pop-ups adapt to the orientation: they drop *below* a horizontal top-level
2740
+ * item and fly out to the side for vertical menus / deeper levels, flipping
2741
+ * whenever the preferred side has no room.
2729
2742
  */
2730
- declare class BkMenu {
2743
+ declare class BkMenu implements OnDestroy {
2731
2744
  private readonly host;
2732
2745
  items: MenuItem[];
2733
2746
  orientation: MenuOrientation;
2734
2747
  submenuMode: MenuSubmenuMode;
2748
+ /**
2749
+ * How pop-up submenus open: on `hover` (fly-out follows the pointer and
2750
+ * closes when it leaves) or on `click` (stays put until you click the parent
2751
+ * again, pick a leaf, or click outside). Inline submenus are always click.
2752
+ */
2753
+ trigger: MenuTrigger;
2754
+ /** `compact` shrinks the type, padding and icons for dense layouts. */
2755
+ size: MenuSize;
2735
2756
  /**
2736
2757
  * Keep only one submenu open per level: opening a parent collapses its
2737
2758
  * siblings (and their descendants) so the menu stays compact. Pop-up mode
@@ -2753,32 +2774,56 @@ declare class BkMenu {
2753
2774
  /** Ids of currently expanded/opened parent nodes. */
2754
2775
  private readonly openIds;
2755
2776
  /** Viewport coordinates for each open fixed-position fly-out, keyed by id. */
2756
- readonly popupPos: Map<string, {
2757
- top: number;
2758
- left: number;
2759
- }>;
2777
+ private readonly popupPos;
2778
+ /** Side each open fly-out resolved to — drives the chevron and hover bridge. */
2779
+ private readonly popupSide;
2780
+ /**
2781
+ * Fly-outs whose real size has been measured. Until then the panel is in the
2782
+ * DOM (it has to be, to be measurable) but transparent, so the estimated
2783
+ * position never flashes on the wrong side.
2784
+ */
2785
+ private readonly measuredIds;
2786
+ /** Grace period for the pointer to cross the gap into a fly-out. */
2787
+ private static readonly HOVER_CLOSE_DELAY_MS;
2788
+ private closeTimer;
2760
2789
  constructor(host: ElementRef<HTMLElement>);
2761
2790
  get isVertical(): boolean;
2762
2791
  get isPopup(): boolean;
2792
+ get isCompact(): boolean;
2793
+ get isHoverTrigger(): boolean;
2763
2794
  get maxHeightStyle(): string | null;
2764
2795
  popupTop(item: MenuItem): number | null;
2765
2796
  popupLeft(item: MenuItem): number | null;
2797
+ /** Resolved side of an open fly-out, or null before it has been placed. */
2798
+ submenuSide(item: MenuItem): MenuPopupSide | null;
2799
+ /** True while a fly-out is open but not yet measured — kept transparent. */
2800
+ isPlacing(item: MenuItem): boolean;
2801
+ /** Which way the parent's chevron points. */
2802
+ chevronDir(item: MenuItem, level: number): 'down' | 'right' | 'left';
2766
2803
  hasChildren(item: MenuItem): boolean;
2767
2804
  isOpen(item: MenuItem): boolean;
2768
2805
  isActive(item: MenuItem): boolean;
2769
2806
  isActiveAncestor(item: MenuItem): boolean;
2770
2807
  isHighlighted(item: MenuItem): boolean;
2771
2808
  inlineIndent(level: number): number | null;
2772
- onItemClick(item: MenuItem, siblings: MenuItem[], event: Event): void;
2773
- onItemEnter(item: MenuItem, siblings: MenuItem[], event: MouseEvent, level: number): void;
2774
- private computePopupPos;
2809
+ groupTitleIndent(level: number): number | null;
2810
+ onItemClick(item: MenuItem, siblings: MenuItem[], event: Event, level: number, parent: MenuItem | null): void;
2811
+ onItemEnter(item: MenuItem, siblings: MenuItem[], event: MouseEvent, level: number, parent: MenuItem | null): void;
2775
2812
  onItemLeave(item: MenuItem): void;
2813
+ private cancelPendingClose;
2814
+ ngOnDestroy(): void;
2815
+ private placePopup;
2816
+ private preferredSide;
2817
+ private applyPos;
2818
+ private computePopupPos;
2819
+ private clamp;
2776
2820
  private toggle;
2777
2821
  private closeBranch;
2822
+ private closeAll;
2778
2823
  onDocumentClick(event: MouseEvent): void;
2779
2824
  onViewportChange(): void;
2780
2825
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<BkMenu, never>;
2781
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkMenu, "bk-menu", never, { "items": { "alias": "items"; "required": false; }; "orientation": { "alias": "orientation"; "required": false; }; "submenuMode": { "alias": "submenuMode"; "required": false; }; "singleOpen": { "alias": "singleOpen"; "required": false; }; "activeItemId": { "alias": "activeItemId"; "required": false; }; "tintIcons": { "alias": "tintIcons"; "required": false; }; "maxHeight": { "alias": "maxHeight"; "required": false; }; }, { "activeItemIdChange": "activeItemIdChange"; "itemClick": "itemClick"; }, never, never, true, never>;
2826
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkMenu, "bk-menu", never, { "items": { "alias": "items"; "required": false; }; "orientation": { "alias": "orientation"; "required": false; }; "submenuMode": { "alias": "submenuMode"; "required": false; }; "trigger": { "alias": "trigger"; "required": false; }; "size": { "alias": "size"; "required": false; }; "singleOpen": { "alias": "singleOpen"; "required": false; }; "activeItemId": { "alias": "activeItemId"; "required": false; }; "tintIcons": { "alias": "tintIcons"; "required": false; }; "maxHeight": { "alias": "maxHeight"; "required": false; }; }, { "activeItemIdChange": "activeItemIdChange"; "itemClick": "itemClick"; }, never, never, true, never>;
2782
2827
  }
2783
2828
 
2784
2829
  /** Which edge of the anchor the panel sits against. */
@@ -2993,4 +3038,4 @@ declare class BkAvatarGroup {
2993
3038
  }
2994
3039
 
2995
3040
  export { BKTooltipDirective, BK_DEFAULT_DIALOG_CONFIG, BK_DIALOG_DATA, BK_DIALOG_GLOBAL_CONFIG, BkAvatar, BkAvatarGroup, BkAvatarUploader, BkBadge, BkButton, BkButtonGroup, BkCalendarManagerService, BkCheckbox, BkColumnFilterService, BkColumnSelect, BkCustomCalendar, BkDialogActions, BkDialogClose, BkDialogContent, BkDialogModule, BkDialogRef, BkDialogService, BkDialogTitle, BkDropdown, BkFileCard, BkFilePicker, BkGrid, BkHierarchicalSelect, BkIconButton, BkInput, BkInputChips, BkLoader, BkMenu, BkPagination, BkPill, BkPopover, BkRadioButton, BkScheduledDatePicker, BkSelect, BkSpinner, BkTabs, BkTextarea, BkTimePicker, BkToastr, BkToastrService, BkToggle, BkValidator, BrickclayIcons, BrickclayLib, CalendarModule, CalendarSelection, ColumnFilterOption, NEUTRAL_APPEARANCE, POPOVER_PLACEMENTS, computePopoverPosition, deriveAppearance, getDialogBackdropAnimation, getDialogPanelAnimation, joinPlacement, parseColor, splitPlacement };
2996
- export type { AvatarFallback, AvatarGroupItem, AvatarSize, AvatarVariant, BadgeColor, BadgeSize, BadgeVariant, BkAnimationKeyframes, BkAvatarFallback, BkAvatarSize, BkDialogAnimation, BkDialogConfig, BkDialogPosition, BkInputAutoCapitalize, BkInputAutoComplete, BkInputMode, BkInputType, BkLoaderVariant, BkPageSize, BkTextAreaAutoCapitalize, BkTextAreaAutoComplete, BkTextAreaInputMode, ButtonSize, ButtonVariant, CalendarRange, ColorAppearance, ColorFill, CountryOption, CustomRangesConfig, DotPosition, DotStatus, DropdownItem, DropdownPlacement, DropdownSize, DropdownTrigger, DropdownVariant, FileState, GroupItem, GroupMode, HierarchicalNode, IconButtonSize, IconButtonVariant, IconOrientation, MenuItem, MenuOrientation, MenuSubmenuMode, PillColor, PillSize, PillVariant, PopoverAlign, PopoverPlacement, PopoverPosition, PopoverPositionOptions, PopoverSide, Rect, ScheduledDateSelection, Size, SortDirection, SpinnerSize, TabIconDirection, TabItem, TableAction, TableBadge, TableColumn, TableIcon, TabsColors, TabsOrientation, TabsVariant, TimeConfiguration, ToastConfig, ToastMessage, ToastMethodOptions, ToastPosition, ToastSeverity };
3041
+ export type { AvatarFallback, AvatarGroupItem, AvatarSize, AvatarVariant, BadgeColor, BadgeSize, BadgeVariant, BkAnimationKeyframes, BkAvatarFallback, BkAvatarSize, BkDialogAnimation, BkDialogConfig, BkDialogPosition, BkInputAutoCapitalize, BkInputAutoComplete, BkInputMode, BkInputType, BkLoaderVariant, BkPageSize, BkTextAreaAutoCapitalize, BkTextAreaAutoComplete, BkTextAreaInputMode, ButtonSize, ButtonVariant, CalendarRange, ColorAppearance, ColorFill, CountryOption, CustomRangesConfig, DotPosition, DotStatus, DropdownItem, DropdownPlacement, DropdownSize, DropdownTrigger, DropdownVariant, FileState, GroupItem, GroupMode, HierarchicalNode, IconButtonSize, IconButtonVariant, IconOrientation, MenuItem, MenuOrientation, MenuPopupSide, MenuSize, MenuSubmenuMode, MenuTrigger, PillColor, PillSize, PillVariant, PopoverAlign, PopoverPlacement, PopoverPosition, PopoverPositionOptions, PopoverSide, Rect, ScheduledDateSelection, Size, SortDirection, SpinnerSize, TabIconDirection, TabItem, TableAction, TableBadge, TableColumn, TableIcon, TabsColors, TabsOrientation, TabsVariant, TimeConfiguration, ToastConfig, ToastMessage, ToastMethodOptions, ToastPosition, ToastSeverity };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brickclay-org/ui",
3
- "version": "0.1.76",
3
+ "version": "0.1.77",
4
4
  "schematics": "./schematics/collection.json",
5
5
  "ng-add": {
6
6
  "save": "dependencies"
@@ -16,7 +16,7 @@
16
16
  }
17
17
 
18
18
  .hierarchical-select-control.disabled {
19
- @apply cursor-not-allowed opacity-60;
19
+ @apply cursor-not-allowed;
20
20
  /* !important beats the inline accent (showDots): a disabled control must read
21
21
  as disabled even when a node's colour is applied. */
22
22
  background-color: #f4f4f6 !important;
@@ -164,6 +164,16 @@
164
164
  @apply flex-shrink-0 text-[#141414];
165
165
  }
166
166
 
167
+ /* Selects the parent; the rest of the row still navigates into it. */
168
+ .hierarchical-option-checkbox {
169
+ @apply flex-shrink-0 flex items-center cursor-pointer;
170
+ }
171
+
172
+ /* Matches the sm checkbox (16px) so childless rows keep the column aligned. */
173
+ .hierarchical-option-checkbox-spacer {
174
+ @apply flex-shrink-0 w-4;
175
+ }
176
+
167
177
  .hierarchical-option-empty {
168
178
  @apply px-3 py-2 text-gray-400 cursor-default text-sm;
169
179
  }
@@ -18,11 +18,45 @@
18
18
  --menu-radius: 8px;
19
19
  --menu-disabled: 0.5;
20
20
 
21
+ /* Metrics — the compact variant below is nothing but a second set of these. */
22
+ --menu-font-size: 14px;
23
+ --menu-tracking: -0.28px;
24
+ --menu-pad-y: 10px;
25
+ --menu-pad-x: 12px;
26
+ --menu-gap: 8px;
27
+ --menu-icon-size: 16px;
28
+ --menu-chevron-size: 16px;
29
+ --menu-group-font-size: 11px;
30
+ /* How far a group title sits left of the rows it heads. */
31
+ --menu-title-outdent: 8px;
32
+ --menu-popup-pad: 4px;
33
+ --menu-popup-min-w: 200px;
34
+ --menu-popup-max-w: 280px;
35
+
21
36
  /* Safe to clip to rounded corners: pop-up fly-outs are position:fixed and
22
37
  escape this box entirely, so nothing gets cut off. */
23
- @apply block w-full max-w-full text-sm font-medium tracking-[-0.28px]
38
+ @apply block w-full max-w-full font-medium
24
39
  bg-[var(--menu-bg)] text-[color:var(--menu-text)]
25
40
  border border-[var(--menu-border)] rounded-[var(--menu-radius)] overflow-hidden;
41
+ font-size: var(--menu-font-size);
42
+ letter-spacing: var(--menu-tracking);
43
+ }
44
+
45
+ /* ---- Compact variant: same menu, tighter type and spacing. ---- */
46
+ .bk-menu--compact {
47
+ --menu-radius: 6px;
48
+ --menu-font-size: 12px;
49
+ --menu-tracking: -0.24px;
50
+ --menu-pad-y: 6px;
51
+ --menu-pad-x: 8px;
52
+ --menu-gap: 6px;
53
+ --menu-icon-size: 14px;
54
+ --menu-chevron-size: 14px;
55
+ --menu-group-font-size: 10px;
56
+ --menu-title-outdent: 6px;
57
+ --menu-popup-pad: 3px;
58
+ --menu-popup-min-w: 160px;
59
+ --menu-popup-max-w: 240px;
26
60
  }
27
61
 
28
62
  /* ---- Lists ---- */
@@ -49,11 +83,13 @@
49
83
  }
50
84
 
51
85
  .bk-menu__button {
52
- @apply flex items-center gap-2 w-full py-2.5 px-3 m-0 border-0
86
+ @apply flex items-center w-full m-0 border-0
53
87
  bg-transparent text-inherit text-left cursor-pointer whitespace-nowrap
54
88
  transition-colors duration-100;
55
89
  font: inherit;
56
90
  letter-spacing: inherit;
91
+ gap: var(--menu-gap);
92
+ padding: var(--menu-pad-y) var(--menu-pad-x);
57
93
  }
58
94
 
59
95
  .bk-menu--horizontal > .bk-menu__list > .bk-menu__item > .bk-menu__button {
@@ -99,9 +135,36 @@
99
135
  @apply text-[color:var(--menu-active-text)];
100
136
  }
101
137
 
138
+ /* ---- Group title: names the child group, not selectable.
139
+ Reads as a heading over the rows rather than one of them: outdented a notch
140
+ from the row text, and separated from the group by a rule. Inline and pop-up
141
+ are meant to look identical — the pop-up rules further down only cancel out
142
+ the panel's own padding to get there. ---- */
143
+ .bk-menu__group-title {
144
+ @apply font-semibold uppercase tracking-wider text-[color:var(--menu-subtext)]
145
+ whitespace-nowrap overflow-hidden text-ellipsis select-none
146
+ border-b border-[var(--menu-border)] mb-1;
147
+ font-size: var(--menu-group-font-size);
148
+ padding: var(--menu-pad-y) var(--menu-pad-x) calc(var(--menu-pad-y) / 2);
149
+ /* Inline levels set padding-left themselves, indent-aware, from the template. */
150
+ padding-left: calc(var(--menu-pad-x) - var(--menu-title-outdent));
151
+ }
152
+
153
+ /* The panel is padded so its rows sit in from the border; the title's rule is
154
+ supposed to span it edge to edge, so pull it back out and pad the text back in.
155
+ Net indent and rule width then match the inline case exactly. */
156
+ .bk-menu__submenu--popup > .bk-menu__group-title {
157
+ margin-left: calc(var(--menu-popup-pad) * -1);
158
+ margin-right: calc(var(--menu-popup-pad) * -1);
159
+ padding-left: calc(var(--menu-pad-x) - var(--menu-title-outdent) + var(--menu-popup-pad));
160
+ padding-right: calc(var(--menu-pad-x) + var(--menu-popup-pad));
161
+ }
162
+
102
163
  /* ---- Icon (optional). Tinted monochrome when .bk-menu--tint. ---- */
103
164
  .bk-menu__icon {
104
- @apply w-4 h-4 shrink-0 object-contain;
165
+ @apply shrink-0 object-contain;
166
+ width: var(--menu-icon-size);
167
+ height: var(--menu-icon-size);
105
168
  }
106
169
 
107
170
  .bk-menu--tint .bk-menu__icon {
@@ -124,6 +187,8 @@
124
187
  /* ---- Chevron ---- */
125
188
  .bk-menu__chevron {
126
189
  @apply shrink-0 opacity-80 transition-transform duration-[180ms];
190
+ width: var(--menu-chevron-size);
191
+ height: var(--menu-chevron-size);
127
192
  }
128
193
 
129
194
  /* Inline: rotate down → up on open. */
@@ -131,11 +196,15 @@
131
196
  @apply rotate-180;
132
197
  }
133
198
 
134
- /* Pop-up fly-out: static caret pointing to where the panel opens (right). */
199
+ /* Pop-up fly-out: static caret pointing at the side the panel opens on. */
135
200
  .bk-menu__chevron--right {
136
201
  @apply -rotate-90;
137
202
  }
138
203
 
204
+ .bk-menu__chevron--left {
205
+ @apply rotate-90;
206
+ }
207
+
139
208
  /* =========================================================================
140
209
  Inline submenus (accordion) — expand in place.
141
210
  ========================================================================= */
@@ -153,26 +222,29 @@
153
222
  .bk-menu__submenu--popup {
154
223
  /* Fixed so the fly-out escapes the menu's overflow/scroll box and renders
155
224
  outside the nav. Coordinates are set inline (top/left) from JS. */
156
- @apply fixed z-[1000] min-w-[200px] max-w-[280px] max-h-[calc(100vh-16px)]
225
+ @apply fixed z-[1000] max-h-[calc(100vh-16px)]
157
226
  overflow-y-auto bg-[var(--menu-bg)] border border-[var(--menu-border)]
158
- rounded-xl shadow-[var(--menu-shadow)] p-1 hidden;
227
+ rounded-xl shadow-[var(--menu-shadow)] hidden;
228
+ padding: var(--menu-popup-pad);
229
+ min-width: var(--menu-popup-min-w);
230
+ max-width: var(--menu-popup-max-w);
159
231
  }
160
232
 
161
233
  .bk-menu__submenu--popup.bk-menu__submenu--open {
162
234
  @apply block;
163
235
  }
164
236
 
165
- /* Invisible bridges spanning the gap between a parent and its fly-out, so the
166
- pointer stays "inside" the item while crossing prevents hover flicker. */
167
- .bk-menu__submenu--popup-right::before {
168
- content: '';
169
- @apply absolute top-0 left-[-6px] w-1.5 h-full;
170
- }
171
- .bk-menu__submenu--popup-down::before {
172
- content: '';
173
- @apply absolute top-[-6px] left-0 w-full h-1.5;
237
+ /* Open but not yet measured: on-screen so its size can be read, invisible so
238
+ the estimated position never flashes before the real one lands. */
239
+ .bk-menu__submenu--placing {
240
+ @apply opacity-0 pointer-events-none;
174
241
  }
175
242
 
243
+ /* The gap between a parent and its fly-out is dead space the pointer has to
244
+ cross, and crossing it fires mouseleave. An invisible bridge element can't
245
+ span it — the panel is a scroll container, so anything at a negative offset
246
+ is clipped — so hover mode closes on a short delay instead (see menu.ts). */
247
+
176
248
  /* =========================================================================
177
249
  Responsive: a horizontal menu STAYS horizontal on small screens and scrolls
178
250
  sideways when the items don't fit (overflow-x: auto). Items keep their