@aquera/nile-elements 1.9.4 → 1.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +7 -0
  2. package/dist/index-5fef1a0d.esm.js +1 -0
  3. package/dist/index-8f38fae3.cjs.js +2 -0
  4. package/dist/index-8f38fae3.cjs.js.map +1 -0
  5. package/dist/index.cjs.js +1 -1
  6. package/dist/index.esm.js +1 -1
  7. package/dist/index.js +1287 -736
  8. package/dist/nile-combobox/index.cjs.js +1 -1
  9. package/dist/nile-combobox/index.esm.js +1 -1
  10. package/dist/nile-combobox/nile-combobox.cjs.js +1 -1
  11. package/dist/nile-combobox/nile-combobox.esm.js +1 -1
  12. package/dist/nile-command-menu/index.cjs.js +2 -0
  13. package/dist/nile-command-menu/index.cjs.js.map +1 -0
  14. package/dist/nile-command-menu/index.esm.js +1 -0
  15. package/dist/nile-command-menu/nile-command-menu.cjs.js +2 -0
  16. package/dist/nile-command-menu/nile-command-menu.cjs.js.map +1 -0
  17. package/dist/nile-command-menu/nile-command-menu.css.cjs.js +2 -0
  18. package/dist/nile-command-menu/nile-command-menu.css.cjs.js.map +1 -0
  19. package/dist/nile-command-menu/nile-command-menu.css.esm.js +182 -0
  20. package/dist/nile-command-menu/nile-command-menu.esm.js +97 -0
  21. package/dist/nile-command-menu-group/index.cjs.js +2 -0
  22. package/dist/nile-command-menu-group/index.cjs.js.map +1 -0
  23. package/dist/nile-command-menu-group/index.esm.js +1 -0
  24. package/dist/nile-command-menu-group/nile-command-menu-group.cjs.js +2 -0
  25. package/dist/nile-command-menu-group/nile-command-menu-group.cjs.js.map +1 -0
  26. package/dist/nile-command-menu-group/nile-command-menu-group.css.cjs.js +2 -0
  27. package/dist/nile-command-menu-group/nile-command-menu-group.css.cjs.js.map +1 -0
  28. package/dist/nile-command-menu-group/nile-command-menu-group.css.esm.js +33 -0
  29. package/dist/nile-command-menu-group/nile-command-menu-group.esm.js +11 -0
  30. package/dist/nile-command-menu-item/index.cjs.js +2 -0
  31. package/dist/nile-command-menu-item/index.cjs.js.map +1 -0
  32. package/dist/nile-command-menu-item/index.esm.js +1 -0
  33. package/dist/nile-command-menu-item/nile-command-menu-item.cjs.js +2 -0
  34. package/dist/nile-command-menu-item/nile-command-menu-item.cjs.js.map +1 -0
  35. package/dist/nile-command-menu-item/nile-command-menu-item.css.cjs.js +2 -0
  36. package/dist/nile-command-menu-item/nile-command-menu-item.css.cjs.js.map +1 -0
  37. package/dist/nile-command-menu-item/nile-command-menu-item.css.esm.js +115 -0
  38. package/dist/nile-command-menu-item/nile-command-menu-item.esm.js +21 -0
  39. package/dist/nile-detail/index.cjs.js +1 -1
  40. package/dist/nile-detail/index.esm.js +1 -1
  41. package/dist/nile-detail/nile-detail.cjs.js +1 -1
  42. package/dist/nile-detail/nile-detail.esm.js +1 -1
  43. package/dist/nile-dialog/nile-dialog.cjs.js +1 -1
  44. package/dist/nile-dialog/nile-dialog.cjs.js.map +1 -1
  45. package/dist/nile-dialog/nile-dialog.esm.js +1 -1
  46. package/dist/nile-dot-stepper/index.cjs.js +2 -0
  47. package/dist/nile-dot-stepper/index.cjs.js.map +1 -0
  48. package/dist/nile-dot-stepper/index.esm.js +1 -0
  49. package/dist/nile-dot-stepper/nile-dot-stepper.cjs.js +2 -0
  50. package/dist/nile-dot-stepper/nile-dot-stepper.cjs.js.map +1 -0
  51. package/dist/nile-dot-stepper/nile-dot-stepper.css.cjs.js +2 -0
  52. package/dist/nile-dot-stepper/nile-dot-stepper.css.cjs.js.map +1 -0
  53. package/dist/nile-dot-stepper/nile-dot-stepper.css.esm.js +77 -0
  54. package/dist/nile-dot-stepper/nile-dot-stepper.esm.js +24 -0
  55. package/dist/nile-section-message/nile-section-message.css.cjs.js +1 -1
  56. package/dist/nile-section-message/nile-section-message.css.cjs.js.map +1 -1
  57. package/dist/nile-section-message/nile-section-message.css.esm.js +7 -8
  58. package/dist/src/index.d.ts +4 -0
  59. package/dist/src/index.js +4 -0
  60. package/dist/src/index.js.map +1 -1
  61. package/dist/src/nile-command-menu/index.d.ts +1 -0
  62. package/dist/src/nile-command-menu/index.js +2 -0
  63. package/dist/src/nile-command-menu/index.js.map +1 -0
  64. package/dist/src/nile-command-menu/nile-command-menu.css.d.ts +12 -0
  65. package/dist/src/nile-command-menu/nile-command-menu.css.js +194 -0
  66. package/dist/src/nile-command-menu/nile-command-menu.css.js.map +1 -0
  67. package/dist/src/nile-command-menu/nile-command-menu.d.ts +137 -0
  68. package/dist/src/nile-command-menu/nile-command-menu.js +557 -0
  69. package/dist/src/nile-command-menu/nile-command-menu.js.map +1 -0
  70. package/dist/src/nile-command-menu/nile-command-menu.test.d.ts +3 -0
  71. package/dist/src/nile-command-menu/nile-command-menu.test.js +268 -0
  72. package/dist/src/nile-command-menu/nile-command-menu.test.js.map +1 -0
  73. package/dist/src/nile-command-menu-group/index.d.ts +1 -0
  74. package/dist/src/nile-command-menu-group/index.js +2 -0
  75. package/dist/src/nile-command-menu-group/index.js.map +1 -0
  76. package/dist/src/nile-command-menu-group/nile-command-menu-group.css.d.ts +12 -0
  77. package/dist/src/nile-command-menu-group/nile-command-menu-group.css.js +45 -0
  78. package/dist/src/nile-command-menu-group/nile-command-menu-group.css.js.map +1 -0
  79. package/dist/src/nile-command-menu-group/nile-command-menu-group.d.ts +38 -0
  80. package/dist/src/nile-command-menu-group/nile-command-menu-group.js +68 -0
  81. package/dist/src/nile-command-menu-group/nile-command-menu-group.js.map +1 -0
  82. package/dist/src/nile-command-menu-group/nile-command-menu-group.test.d.ts +2 -0
  83. package/dist/src/nile-command-menu-group/nile-command-menu-group.test.js +22 -0
  84. package/dist/src/nile-command-menu-group/nile-command-menu-group.test.js.map +1 -0
  85. package/dist/src/nile-command-menu-item/index.d.ts +1 -0
  86. package/dist/src/nile-command-menu-item/index.js +2 -0
  87. package/dist/src/nile-command-menu-item/index.js.map +1 -0
  88. package/dist/src/nile-command-menu-item/nile-command-menu-item.css.d.ts +12 -0
  89. package/dist/src/nile-command-menu-item/nile-command-menu-item.css.js +127 -0
  90. package/dist/src/nile-command-menu-item/nile-command-menu-item.css.js.map +1 -0
  91. package/dist/src/nile-command-menu-item/nile-command-menu-item.d.ts +73 -0
  92. package/dist/src/nile-command-menu-item/nile-command-menu-item.js +174 -0
  93. package/dist/src/nile-command-menu-item/nile-command-menu-item.js.map +1 -0
  94. package/dist/src/nile-command-menu-item/nile-command-menu-item.test.d.ts +1 -0
  95. package/dist/src/nile-command-menu-item/nile-command-menu-item.test.js +35 -0
  96. package/dist/src/nile-command-menu-item/nile-command-menu-item.test.js.map +1 -0
  97. package/dist/src/nile-dialog/nile-dialog.js +1 -1
  98. package/dist/src/nile-dialog/nile-dialog.js.map +1 -1
  99. package/dist/src/nile-dot-stepper/index.d.ts +1 -0
  100. package/dist/src/nile-dot-stepper/index.js +2 -0
  101. package/dist/src/nile-dot-stepper/index.js.map +1 -0
  102. package/dist/src/nile-dot-stepper/nile-dot-stepper.css.d.ts +12 -0
  103. package/dist/src/nile-dot-stepper/nile-dot-stepper.css.js +89 -0
  104. package/dist/src/nile-dot-stepper/nile-dot-stepper.css.js.map +1 -0
  105. package/dist/src/nile-dot-stepper/nile-dot-stepper.d.ts +48 -0
  106. package/dist/src/nile-dot-stepper/nile-dot-stepper.js +153 -0
  107. package/dist/src/nile-dot-stepper/nile-dot-stepper.js.map +1 -0
  108. package/dist/src/nile-dot-stepper/nile-dot-stepper.test.d.ts +1 -0
  109. package/dist/src/nile-dot-stepper/nile-dot-stepper.test.js +319 -0
  110. package/dist/src/nile-dot-stepper/nile-dot-stepper.test.js.map +1 -0
  111. package/dist/src/nile-section-message/nile-section-message.css.js +7 -8
  112. package/dist/src/nile-section-message/nile-section-message.css.js.map +1 -1
  113. package/dist/src/version.js +1 -1
  114. package/dist/src/version.js.map +1 -1
  115. package/dist/tsconfig.tsbuildinfo +1 -1
  116. package/package.json +2 -1
  117. package/src/index.ts +4 -0
  118. package/src/nile-command-menu/index.ts +1 -0
  119. package/src/nile-command-menu/nile-command-menu.css.ts +196 -0
  120. package/src/nile-command-menu/nile-command-menu.test.ts +289 -0
  121. package/src/nile-command-menu/nile-command-menu.ts +601 -0
  122. package/src/nile-command-menu-group/index.ts +1 -0
  123. package/src/nile-command-menu-group/nile-command-menu-group.css.ts +47 -0
  124. package/src/nile-command-menu-group/nile-command-menu-group.test.ts +23 -0
  125. package/src/nile-command-menu-group/nile-command-menu-group.ts +72 -0
  126. package/src/nile-command-menu-item/index.ts +1 -0
  127. package/src/nile-command-menu-item/nile-command-menu-item.css.ts +129 -0
  128. package/src/nile-command-menu-item/nile-command-menu-item.test.ts +36 -0
  129. package/src/nile-command-menu-item/nile-command-menu-item.ts +172 -0
  130. package/src/nile-dialog/nile-dialog.ts +1 -1
  131. package/src/nile-dot-stepper/index.ts +1 -0
  132. package/src/nile-dot-stepper/nile-dot-stepper.css.ts +90 -0
  133. package/src/nile-dot-stepper/nile-dot-stepper.test.ts +493 -0
  134. package/src/nile-dot-stepper/nile-dot-stepper.ts +159 -0
  135. package/src/nile-section-message/nile-section-message.css.ts +7 -8
  136. package/stories/dot-stepper.stories.ts +67 -0
  137. package/vscode-html-custom-data.json +179 -0
  138. package/dist/index-c0b5232c.cjs.js +0 -2
  139. package/dist/index-c0b5232c.cjs.js.map +0 -1
  140. package/dist/index-e43a18eb.esm.js +0 -1
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Copyright Aquera Inc 2023
3
+ *
4
+ * This source code is licensed under the BSD-3-Clause license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import NileElement from '../internal/nile-element';
8
+ import '../nile-tag/nile-tag';
9
+ import type { CSSResultGroup } from 'lit';
10
+ /** A removable search scope shown in the "Searching for" row. */
11
+ export interface CommandMenuScope {
12
+ label: string;
13
+ value: string;
14
+ }
15
+ /**
16
+ * A keyboard-driven command palette for fast, page-wide search and action execution.
17
+ *
18
+ * @tag nile-command-menu
19
+ *
20
+ * @summary A modal command menu (command palette) with built-in search, grouping and keyboard navigation.
21
+ * @status stable
22
+ * @since 2.0
23
+ *
24
+ * @dependency nile-glyph
25
+ * @dependency nile-tag
26
+ *
27
+ * @slot - The command content: `nile-command-menu-group` and/or `nile-command-menu-item` elements.
28
+ * @slot empty - Custom content shown when no items match the current query.
29
+ * @slot footer - Optional footer content. When empty, a built-in keyboard-hint row is shown (unless `nofooterhints`).
30
+ *
31
+ * @event nile-show - Emitted when the menu opens.
32
+ * @event nile-after-show - Emitted after the menu opens and the animation completes.
33
+ * @event nile-hide - Emitted when the menu starts to close.
34
+ * @event nile-after-hide - Emitted after the menu closes and the animation completes.
35
+ * @event nile-request-close - Emitted when the user attempts to close the menu (overlay click / Escape). Cancelable.
36
+ * @event nile-search - Emitted when the search value changes. `detail: { value }`.
37
+ * @event nile-select - Emitted when an item is selected. `detail: { value, item }`.
38
+ * @event nile-scope-remove - Emitted when a scope tag is removed. `detail: { scope, scopes }`.
39
+ *
40
+ * @csspart base - The component's outer wrapper.
41
+ * @csspart overlay - The overlay behind the panel.
42
+ * @csspart panel - The command panel.
43
+ * @csspart header - The search header.
44
+ * @csspart input - The search input.
45
+ * @csspart scopes - The "Searching for" scopes row.
46
+ * @csspart scope-tag - Each scope tag in the scopes row.
47
+ * @csspart list - The scrollable results list.
48
+ * @csspart empty - The empty state shown when nothing matches.
49
+ * @csspart footer - The footer.
50
+ * @csspart hints - The built-in keyboard-hint row inside the footer.
51
+ *
52
+ * @cssproperty --width - The maximum width of the panel.
53
+ * @cssproperty --max-height - The maximum height of the panel.
54
+ */
55
+ export declare class NileCommandMenu extends NileElement {
56
+ static styles: CSSResultGroup;
57
+ private readonly hasSlotController;
58
+ private modal;
59
+ private originalTrigger;
60
+ command: HTMLElement;
61
+ panel: HTMLElement;
62
+ overlay: HTMLElement;
63
+ input: HTMLInputElement;
64
+ /** Indicates whether the menu is open. Toggle this attribute, or use `show()` / `hide()`. */
65
+ open: boolean;
66
+ /** Placeholder text shown in the search input. */
67
+ placeholder: string;
68
+ /** Accessible label for the dialog and the search input. */
69
+ label: string;
70
+ /** Message shown when no items match the current query. Ignored when the `empty` slot is used. */
71
+ noResultsMessage: string;
72
+ /** The current search value. */
73
+ value: string;
74
+ /**
75
+ * When set, the menu does not filter items itself; instead it emits `nile-search` so the host can
76
+ * manage results. Useful for async/server-side search.
77
+ */
78
+ customSearch: boolean;
79
+ /** Keeps the menu open after an item is selected instead of closing it. */
80
+ keepOpenOnSelect: boolean;
81
+ /** Prevents the menu from closing when the overlay is clicked. */
82
+ preventOverlayClose: boolean;
83
+ /** Hides the built-in keyboard-hint footer (navigate / select / close). The `footer` slot always overrides it. */
84
+ noFooterHints: boolean;
85
+ /**
86
+ * Active search scopes shown as removable tags in the "Searching for" row. Items are filtered so that
87
+ * only those with no `scope` or whose `scope` matches an active scope are shown. Set via the `scopes`
88
+ * attribute (comma-separated labels or a JSON array) or the property (array of `{ label, value }`).
89
+ */
90
+ scopes: CommandMenuScope[];
91
+ /** Label shown before the scope tags. */
92
+ scopesLabel: string;
93
+ /** True when the current query matches no items. */
94
+ private hasResults;
95
+ connectedCallback(): void;
96
+ firstUpdated(): void;
97
+ disconnectedCallback(): void;
98
+ /** Gets all command items in the menu (across groups), in document order. */
99
+ private getAllItems;
100
+ /** Gets the items that are currently visible (matching the query) and not disabled. */
101
+ private getVisibleItems;
102
+ private getActiveItem;
103
+ private setActiveItem;
104
+ /** Applies the current query to the items, hiding non-matching items and empty groups. */
105
+ private filterItems;
106
+ /** Hides groups that have no visible items. */
107
+ private updateGroupVisibility;
108
+ /** Ensures exactly one visible item is active, defaulting to the first. */
109
+ private ensureActiveItem;
110
+ private handleInput;
111
+ handleScopesChange(): void;
112
+ private handleScopeRemove;
113
+ private handleSlotChange;
114
+ private handlePanelKeyDown;
115
+ private handleListClick;
116
+ private selectItem;
117
+ private requestClose;
118
+ private addOpenListeners;
119
+ private removeOpenListeners;
120
+ private handleDocumentKeyDown;
121
+ handleOpenChange(): Promise<void>;
122
+ /** Renders the "Searching for" scopes row when scopes are active. */
123
+ private renderScopes;
124
+ /** Renders the built-in keyboard-hint footer (used as the `footer` slot's fallback content). */
125
+ private renderHints;
126
+ /** Shows the command menu. */
127
+ show(): Promise<void>;
128
+ /** Hides the command menu. */
129
+ hide(): Promise<void>;
130
+ render(): import("lit-html").TemplateResult<1>;
131
+ }
132
+ export default NileCommandMenu;
133
+ declare global {
134
+ interface HTMLElementTagNameMap {
135
+ 'nile-command-menu': NileCommandMenu;
136
+ }
137
+ }
@@ -0,0 +1,557 @@
1
+ /**
2
+ * Copyright Aquera Inc 2023
3
+ *
4
+ * This source code is licensed under the BSD-3-Clause license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import { __decorate } from "tslib";
8
+ import { html, nothing } from 'lit';
9
+ import { customElement, property, query, state } from 'lit/decorators.js';
10
+ import { classMap } from 'lit/directives/class-map.js';
11
+ import { animateTo, stopAnimations } from '../internal/animate';
12
+ import { getAnimation, setDefaultAnimation } from '../utilities/animation-registry';
13
+ import { HasSlotController } from '../internal/slot';
14
+ import { lockBodyScrolling, unlockBodyScrolling } from '../internal/scroll';
15
+ import { waitForEvent } from '../internal/event';
16
+ import { watch } from '../internal/watch';
17
+ import Modal from '../internal/modal';
18
+ import NileElement from '../internal/nile-element';
19
+ import { styles } from './nile-command-menu.css';
20
+ import '../nile-tag/nile-tag';
21
+ let commandMenuItemId = 0;
22
+ function scopeFromValue(entry) {
23
+ if (typeof entry === 'string') {
24
+ return { label: entry, value: entry.toLowerCase() };
25
+ }
26
+ return { label: entry.label, value: entry.value ?? entry.label.toLowerCase() };
27
+ }
28
+ /** Normalises the `scopes` attribute (comma-separated string or JSON array) into scope objects. */
29
+ const scopesConverter = {
30
+ fromAttribute(value) {
31
+ if (!value) {
32
+ return [];
33
+ }
34
+ const trimmed = value.trim();
35
+ if (trimmed.startsWith('[')) {
36
+ try {
37
+ const parsed = JSON.parse(trimmed);
38
+ return (Array.isArray(parsed) ? parsed : []).map(scopeFromValue);
39
+ }
40
+ catch {
41
+ // fall through to comma-separated parsing
42
+ }
43
+ }
44
+ return trimmed
45
+ .split(',')
46
+ .map(part => part.trim())
47
+ .filter(Boolean)
48
+ .map(scopeFromValue);
49
+ },
50
+ toAttribute(value) {
51
+ return (value ?? []).map(scope => scope.label).join(', ');
52
+ },
53
+ };
54
+ /**
55
+ * A keyboard-driven command palette for fast, page-wide search and action execution.
56
+ *
57
+ * @tag nile-command-menu
58
+ *
59
+ * @summary A modal command menu (command palette) with built-in search, grouping and keyboard navigation.
60
+ * @status stable
61
+ * @since 2.0
62
+ *
63
+ * @dependency nile-glyph
64
+ * @dependency nile-tag
65
+ *
66
+ * @slot - The command content: `nile-command-menu-group` and/or `nile-command-menu-item` elements.
67
+ * @slot empty - Custom content shown when no items match the current query.
68
+ * @slot footer - Optional footer content. When empty, a built-in keyboard-hint row is shown (unless `nofooterhints`).
69
+ *
70
+ * @event nile-show - Emitted when the menu opens.
71
+ * @event nile-after-show - Emitted after the menu opens and the animation completes.
72
+ * @event nile-hide - Emitted when the menu starts to close.
73
+ * @event nile-after-hide - Emitted after the menu closes and the animation completes.
74
+ * @event nile-request-close - Emitted when the user attempts to close the menu (overlay click / Escape). Cancelable.
75
+ * @event nile-search - Emitted when the search value changes. `detail: { value }`.
76
+ * @event nile-select - Emitted when an item is selected. `detail: { value, item }`.
77
+ * @event nile-scope-remove - Emitted when a scope tag is removed. `detail: { scope, scopes }`.
78
+ *
79
+ * @csspart base - The component's outer wrapper.
80
+ * @csspart overlay - The overlay behind the panel.
81
+ * @csspart panel - The command panel.
82
+ * @csspart header - The search header.
83
+ * @csspart input - The search input.
84
+ * @csspart scopes - The "Searching for" scopes row.
85
+ * @csspart scope-tag - Each scope tag in the scopes row.
86
+ * @csspart list - The scrollable results list.
87
+ * @csspart empty - The empty state shown when nothing matches.
88
+ * @csspart footer - The footer.
89
+ * @csspart hints - The built-in keyboard-hint row inside the footer.
90
+ *
91
+ * @cssproperty --width - The maximum width of the panel.
92
+ * @cssproperty --max-height - The maximum height of the panel.
93
+ */
94
+ let NileCommandMenu = class NileCommandMenu extends NileElement {
95
+ constructor() {
96
+ super(...arguments);
97
+ this.hasSlotController = new HasSlotController(this, 'footer');
98
+ /** Indicates whether the menu is open. Toggle this attribute, or use `show()` / `hide()`. */
99
+ this.open = false;
100
+ /** Placeholder text shown in the search input. */
101
+ this.placeholder = 'Search commands...';
102
+ /** Accessible label for the dialog and the search input. */
103
+ this.label = 'Command menu';
104
+ /** Message shown when no items match the current query. Ignored when the `empty` slot is used. */
105
+ this.noResultsMessage = 'No results found';
106
+ /** The current search value. */
107
+ this.value = '';
108
+ /**
109
+ * When set, the menu does not filter items itself; instead it emits `nile-search` so the host can
110
+ * manage results. Useful for async/server-side search.
111
+ */
112
+ this.customSearch = false;
113
+ /** Keeps the menu open after an item is selected instead of closing it. */
114
+ this.keepOpenOnSelect = false;
115
+ /** Prevents the menu from closing when the overlay is clicked. */
116
+ this.preventOverlayClose = false;
117
+ /** Hides the built-in keyboard-hint footer (navigate / select / close). The `footer` slot always overrides it. */
118
+ this.noFooterHints = false;
119
+ /**
120
+ * Active search scopes shown as removable tags in the "Searching for" row. Items are filtered so that
121
+ * only those with no `scope` or whose `scope` matches an active scope are shown. Set via the `scopes`
122
+ * attribute (comma-separated labels or a JSON array) or the property (array of `{ label, value }`).
123
+ */
124
+ this.scopes = [];
125
+ /** Label shown before the scope tags. */
126
+ this.scopesLabel = 'Searching for';
127
+ /** True when the current query matches no items. */
128
+ this.hasResults = true;
129
+ }
130
+ connectedCallback() {
131
+ super.connectedCallback();
132
+ this.handleDocumentKeyDown = this.handleDocumentKeyDown.bind(this);
133
+ this.modal = new Modal(this);
134
+ }
135
+ firstUpdated() {
136
+ this.command.hidden = !this.open;
137
+ if (this.open) {
138
+ this.addOpenListeners();
139
+ this.modal.activate();
140
+ lockBodyScrolling(this);
141
+ }
142
+ }
143
+ disconnectedCallback() {
144
+ super.disconnectedCallback();
145
+ unlockBodyScrolling(this);
146
+ }
147
+ /** Gets all command items in the menu (across groups), in document order. */
148
+ getAllItems() {
149
+ return [...this.querySelectorAll('nile-command-menu-item')];
150
+ }
151
+ /** Gets the items that are currently visible (matching the query) and not disabled. */
152
+ getVisibleItems() {
153
+ return this.getAllItems().filter(item => !item.hidden && !item.disabled);
154
+ }
155
+ getActiveItem() {
156
+ return this.getVisibleItems().find(item => item.active);
157
+ }
158
+ setActiveItem(item) {
159
+ this.getAllItems().forEach(i => {
160
+ i.active = i === item;
161
+ });
162
+ if (item) {
163
+ if (!item.id) {
164
+ item.id = `nile-command-menu-item-${++commandMenuItemId}`;
165
+ }
166
+ this.input?.setAttribute('aria-activedescendant', item.id);
167
+ item.scrollIntoView({ block: 'nearest' });
168
+ }
169
+ else {
170
+ this.input?.removeAttribute('aria-activedescendant');
171
+ }
172
+ }
173
+ /** Applies the current query to the items, hiding non-matching items and empty groups. */
174
+ filterItems() {
175
+ const items = this.getAllItems();
176
+ if (this.customSearch) {
177
+ // The host owns visibility; just refresh the empty state and active item.
178
+ this.hasResults = this.getVisibleItems().length > 0;
179
+ this.updateGroupVisibility();
180
+ this.ensureActiveItem();
181
+ return;
182
+ }
183
+ const query = this.value.trim().toLowerCase();
184
+ const activeScopes = this.scopes.map(scope => scope.value);
185
+ items.forEach(item => {
186
+ const matchesQuery = query.length === 0 || item.getFilterText().includes(query);
187
+ const matchesScope = activeScopes.length === 0 || !item.scope || activeScopes.includes(item.scope);
188
+ item.hidden = !(matchesQuery && matchesScope);
189
+ });
190
+ this.updateGroupVisibility();
191
+ this.hasResults = this.getVisibleItems().length > 0;
192
+ this.ensureActiveItem();
193
+ }
194
+ /** Hides groups that have no visible items. */
195
+ updateGroupVisibility() {
196
+ const groups = [...this.querySelectorAll('nile-command-menu-group')];
197
+ groups.forEach(group => {
198
+ const hasVisibleItem = [
199
+ ...group.querySelectorAll('nile-command-menu-item'),
200
+ ].some(item => !item.hidden);
201
+ group.hidden = !hasVisibleItem;
202
+ });
203
+ }
204
+ /** Ensures exactly one visible item is active, defaulting to the first. */
205
+ ensureActiveItem() {
206
+ const visible = this.getVisibleItems();
207
+ const current = this.getActiveItem();
208
+ if (visible.length === 0) {
209
+ this.setActiveItem(undefined);
210
+ return;
211
+ }
212
+ if (!current || !visible.includes(current)) {
213
+ this.setActiveItem(visible[0]);
214
+ }
215
+ }
216
+ handleInput(event) {
217
+ this.value = event.target.value;
218
+ this.emit('nile-search', { value: this.value });
219
+ this.filterItems();
220
+ }
221
+ handleScopesChange() {
222
+ this.filterItems();
223
+ }
224
+ handleScopeRemove(scope) {
225
+ this.scopes = this.scopes.filter(s => s.value !== scope.value);
226
+ this.emit('nile-scope-remove', { scope, scopes: this.scopes });
227
+ this.filterItems();
228
+ }
229
+ handleSlotChange() {
230
+ this.filterItems();
231
+ }
232
+ handlePanelKeyDown(event) {
233
+ const visible = this.getVisibleItems();
234
+ if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) {
235
+ if (visible.length === 0) {
236
+ return;
237
+ }
238
+ event.preventDefault();
239
+ const current = this.getActiveItem();
240
+ let index = current ? visible.indexOf(current) : -1;
241
+ if (event.key === 'ArrowDown')
242
+ index++;
243
+ else if (event.key === 'ArrowUp')
244
+ index--;
245
+ else if (event.key === 'Home')
246
+ index = 0;
247
+ else if (event.key === 'End')
248
+ index = visible.length - 1;
249
+ if (index < 0)
250
+ index = visible.length - 1;
251
+ if (index > visible.length - 1)
252
+ index = 0;
253
+ this.setActiveItem(visible[index]);
254
+ return;
255
+ }
256
+ if (event.key === 'Enter') {
257
+ const current = this.getActiveItem();
258
+ if (current) {
259
+ event.preventDefault();
260
+ this.selectItem(current);
261
+ }
262
+ }
263
+ }
264
+ handleListClick(event) {
265
+ const item = event.target.closest('nile-command-menu-item');
266
+ if (item && !item.disabled) {
267
+ this.selectItem(item);
268
+ }
269
+ }
270
+ selectItem(item) {
271
+ this.emit('nile-select', { value: item.value, item });
272
+ if (!this.keepOpenOnSelect) {
273
+ this.hide();
274
+ }
275
+ }
276
+ requestClose(source) {
277
+ const requestClose = this.emit('nile-request-close', { source }, undefined, undefined, true);
278
+ if (requestClose.defaultPrevented) {
279
+ return;
280
+ }
281
+ if (source === 'overlay' && this.preventOverlayClose) {
282
+ return;
283
+ }
284
+ this.hide();
285
+ }
286
+ addOpenListeners() {
287
+ document.addEventListener('keydown', this.handleDocumentKeyDown);
288
+ }
289
+ removeOpenListeners() {
290
+ document.removeEventListener('keydown', this.handleDocumentKeyDown);
291
+ }
292
+ handleDocumentKeyDown(event) {
293
+ if (this.open && event.key === 'Escape') {
294
+ event.stopPropagation();
295
+ this.requestClose('keyboard');
296
+ }
297
+ }
298
+ async handleOpenChange() {
299
+ if (this.open) {
300
+ this.emit('nile-show');
301
+ this.addOpenListeners();
302
+ this.originalTrigger = document.activeElement;
303
+ this.modal.activate();
304
+ lockBodyScrolling(this);
305
+ await stopAnimations(this.command);
306
+ this.command.hidden = false;
307
+ // Reset query and selection each time the menu opens.
308
+ this.value = '';
309
+ this.filterItems();
310
+ requestAnimationFrame(() => {
311
+ this.input?.focus({ preventScroll: true });
312
+ });
313
+ const panelAnimation = getAnimation(this, 'command.show', { dir: 'ltr' });
314
+ const overlayAnimation = getAnimation(this, 'command.overlay.show', { dir: 'ltr' });
315
+ await Promise.all([
316
+ animateTo(this.panel, panelAnimation.keyframes, panelAnimation.options),
317
+ animateTo(this.overlay, overlayAnimation.keyframes, overlayAnimation.options),
318
+ ]);
319
+ this.emit('nile-after-show');
320
+ }
321
+ else {
322
+ this.emit('nile-hide');
323
+ this.removeOpenListeners();
324
+ this.modal.deactivate();
325
+ await stopAnimations(this.command);
326
+ const panelAnimation = getAnimation(this, 'command.hide', { dir: 'ltr' });
327
+ const overlayAnimation = getAnimation(this, 'command.overlay.hide', { dir: 'ltr' });
328
+ await Promise.all([
329
+ animateTo(this.panel, panelAnimation.keyframes, panelAnimation.options),
330
+ animateTo(this.overlay, overlayAnimation.keyframes, overlayAnimation.options),
331
+ ]);
332
+ this.command.hidden = true;
333
+ unlockBodyScrolling(this);
334
+ const trigger = this.originalTrigger;
335
+ if (typeof trigger?.focus === 'function') {
336
+ setTimeout(() => trigger.focus());
337
+ }
338
+ this.emit('nile-after-hide');
339
+ }
340
+ }
341
+ /** Renders the "Searching for" scopes row when scopes are active. */
342
+ renderScopes() {
343
+ if (this.scopes.length === 0) {
344
+ return nothing;
345
+ }
346
+ return html `
347
+ <div part="scopes" class="command__scopes">
348
+ <span class="command__scopes-label">${this.scopesLabel}</span>
349
+ <div class="command__scopes-tags">
350
+ ${this.scopes.map(scope => html `
351
+ <nile-tag
352
+ part="scope-tag"
353
+ exportparts="base:scope-tag__base"
354
+ size="medium"
355
+ variant="normal"
356
+ pill
357
+ removable
358
+ .value=${scope.value}
359
+ @nile-remove=${() => this.handleScopeRemove(scope)}
360
+ >
361
+ ${scope.label}
362
+ </nile-tag>
363
+ `)}
364
+ </div>
365
+ </div>
366
+ `;
367
+ }
368
+ /** Renders the built-in keyboard-hint footer (used as the `footer` slot's fallback content). */
369
+ renderHints() {
370
+ return html `
371
+ <div part="hints" class="command__hints">
372
+ <span class="command__hint"><kbd>↑</kbd><kbd>↓</kbd> to navigate</span>
373
+ <span class="command__hint"><kbd>↵</kbd> to select</span>
374
+ <span class="command__hint"><kbd>esc</kbd> to close</span>
375
+ </div>
376
+ `;
377
+ }
378
+ /** Shows the command menu. */
379
+ async show() {
380
+ if (this.open) {
381
+ return undefined;
382
+ }
383
+ this.open = true;
384
+ return waitForEvent(this, 'nile-after-show');
385
+ }
386
+ /** Hides the command menu. */
387
+ async hide() {
388
+ if (!this.open) {
389
+ return undefined;
390
+ }
391
+ this.open = false;
392
+ return waitForEvent(this, 'nile-after-hide');
393
+ }
394
+ render() {
395
+ return html `
396
+ <div
397
+ part="base"
398
+ class=${classMap({
399
+ command: true,
400
+ 'command--open': this.open,
401
+ 'command--has-footer': this.hasSlotController.test('footer') || !this.noFooterHints,
402
+ })}
403
+ >
404
+ <div
405
+ part="overlay"
406
+ class="command__overlay"
407
+ tabindex="-1"
408
+ @click=${() => this.requestClose('overlay')}
409
+ ></div>
410
+
411
+ <div
412
+ part="panel"
413
+ class="command__panel"
414
+ role="dialog"
415
+ aria-modal="true"
416
+ aria-label=${this.label}
417
+ aria-hidden=${this.open ? 'false' : 'true'}
418
+ @keydown=${this.handlePanelKeyDown}
419
+ >
420
+ <div part="header" class="command__header">
421
+ <span class="command__search-icon" aria-hidden="true">
422
+ <nile-glyph name="search" size="20" method="stroke"></nile-glyph>
423
+ </span>
424
+ <input
425
+ part="input"
426
+ class="command__input"
427
+ type="text"
428
+ role="combobox"
429
+ aria-expanded="true"
430
+ aria-controls="command-list"
431
+ aria-label=${this.label}
432
+ autocomplete="off"
433
+ autocorrect="off"
434
+ autocapitalize="off"
435
+ spellcheck="false"
436
+ .value=${this.value}
437
+ placeholder=${this.placeholder}
438
+ @input=${this.handleInput}
439
+ />
440
+ </div>
441
+
442
+ ${this.renderScopes()}
443
+
444
+ <div
445
+ part="list"
446
+ id="command-list"
447
+ class="command__list"
448
+ role="listbox"
449
+ aria-label=${this.label}
450
+ @click=${this.handleListClick}
451
+ >
452
+ <slot @slotchange=${this.handleSlotChange}></slot>
453
+
454
+ <div
455
+ part="empty"
456
+ class=${classMap({
457
+ command__empty: true,
458
+ 'command__empty--visible': !this.hasResults,
459
+ })}
460
+ >
461
+ <slot name="empty">${!this.hasResults ? this.noResultsMessage : nothing}</slot>
462
+ </div>
463
+ </div>
464
+
465
+ <footer part="footer" class="command__footer">
466
+ <slot name="footer">
467
+ ${this.noFooterHints ? nothing : this.renderHints()}
468
+ </slot>
469
+ </footer>
470
+ </div>
471
+ </div>
472
+ `;
473
+ }
474
+ };
475
+ NileCommandMenu.styles = styles;
476
+ __decorate([
477
+ query('.command')
478
+ ], NileCommandMenu.prototype, "command", void 0);
479
+ __decorate([
480
+ query('.command__panel')
481
+ ], NileCommandMenu.prototype, "panel", void 0);
482
+ __decorate([
483
+ query('.command__overlay')
484
+ ], NileCommandMenu.prototype, "overlay", void 0);
485
+ __decorate([
486
+ query('.command__input')
487
+ ], NileCommandMenu.prototype, "input", void 0);
488
+ __decorate([
489
+ property({ type: Boolean, reflect: true })
490
+ ], NileCommandMenu.prototype, "open", void 0);
491
+ __decorate([
492
+ property()
493
+ ], NileCommandMenu.prototype, "placeholder", void 0);
494
+ __decorate([
495
+ property()
496
+ ], NileCommandMenu.prototype, "label", void 0);
497
+ __decorate([
498
+ property({ attribute: true })
499
+ ], NileCommandMenu.prototype, "noResultsMessage", void 0);
500
+ __decorate([
501
+ property()
502
+ ], NileCommandMenu.prototype, "value", void 0);
503
+ __decorate([
504
+ property({ type: Boolean, attribute: true })
505
+ ], NileCommandMenu.prototype, "customSearch", void 0);
506
+ __decorate([
507
+ property({ type: Boolean, attribute: true })
508
+ ], NileCommandMenu.prototype, "keepOpenOnSelect", void 0);
509
+ __decorate([
510
+ property({ type: Boolean, attribute: true })
511
+ ], NileCommandMenu.prototype, "preventOverlayClose", void 0);
512
+ __decorate([
513
+ property({ type: Boolean, attribute: true })
514
+ ], NileCommandMenu.prototype, "noFooterHints", void 0);
515
+ __decorate([
516
+ property({ converter: scopesConverter })
517
+ ], NileCommandMenu.prototype, "scopes", void 0);
518
+ __decorate([
519
+ property({ attribute: true })
520
+ ], NileCommandMenu.prototype, "scopesLabel", void 0);
521
+ __decorate([
522
+ state()
523
+ ], NileCommandMenu.prototype, "hasResults", void 0);
524
+ __decorate([
525
+ watch('scopes', { waitUntilFirstUpdate: true })
526
+ ], NileCommandMenu.prototype, "handleScopesChange", null);
527
+ __decorate([
528
+ watch('open', { waitUntilFirstUpdate: true })
529
+ ], NileCommandMenu.prototype, "handleOpenChange", null);
530
+ NileCommandMenu = __decorate([
531
+ customElement('nile-command-menu')
532
+ ], NileCommandMenu);
533
+ export { NileCommandMenu };
534
+ setDefaultAnimation('command.show', {
535
+ keyframes: [
536
+ { opacity: 0, scale: 0.95 },
537
+ { opacity: 1, scale: 1 },
538
+ ],
539
+ options: { duration: 200, easing: 'ease' },
540
+ });
541
+ setDefaultAnimation('command.hide', {
542
+ keyframes: [
543
+ { opacity: 1, scale: 1 },
544
+ { opacity: 0, scale: 0.95 },
545
+ ],
546
+ options: { duration: 200, easing: 'ease' },
547
+ });
548
+ setDefaultAnimation('command.overlay.show', {
549
+ keyframes: [{ opacity: 0 }, { opacity: 1 }],
550
+ options: { duration: 200 },
551
+ });
552
+ setDefaultAnimation('command.overlay.hide', {
553
+ keyframes: [{ opacity: 1 }, { opacity: 0 }],
554
+ options: { duration: 200 },
555
+ });
556
+ export default NileCommandMenu;
557
+ //# sourceMappingURL=nile-command-menu.js.map