@unofficialbox/box-open-elements 0.5.0 → 0.6.0
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/README.md +3 -3
- package/dist/components/collections/table.d.ts +57 -3
- package/dist/components/collections/table.js +339 -20
- package/dist/components/feedback/due-badge.d.ts +38 -0
- package/dist/components/feedback/due-badge.js +168 -0
- package/dist/components/feedback/due-types.d.ts +32 -0
- package/dist/components/feedback/due-types.js +91 -0
- package/dist/components/feedback/progress-steps.d.ts +51 -5
- package/dist/components/feedback/progress-steps.js +200 -22
- package/dist/components/feedback/stage-path.d.ts +44 -0
- package/dist/components/feedback/stage-path.js +235 -0
- package/dist/components/forms/select.d.ts +21 -0
- package/dist/components/forms/select.js +100 -3
- package/dist/components/forms/text-field.d.ts +21 -0
- package/dist/components/forms/text-field.js +100 -1
- package/dist/components/layout/nav-sidebar.d.ts +14 -0
- package/dist/components/layout/nav-sidebar.js +49 -0
- package/dist/components/layout/split-view.d.ts +14 -0
- package/dist/components/layout/split-view.js +80 -1
- package/dist/components/overlays/command-palette.d.ts +62 -0
- package/dist/components/overlays/command-palette.js +537 -0
- package/dist/components/overlays/command-types.d.ts +90 -0
- package/dist/components/overlays/command-types.js +259 -0
- package/dist/components/overlays/drawer.d.ts +18 -0
- package/dist/components/overlays/drawer.js +135 -9
- package/dist/components/overlays/shortcuts-overlay.d.ts +48 -0
- package/dist/components/overlays/shortcuts-overlay.js +385 -0
- package/dist/components/overlays/tooltip.js +1 -1
- package/dist/element-maps.d.ts +292 -0
- package/dist/element-maps.js +1 -0
- package/dist/entries/activity-density.d.ts +1 -0
- package/dist/entries/activity-density.js +1 -0
- package/dist/entries/agent-chat.d.ts +1 -0
- package/dist/entries/agent-chat.js +1 -0
- package/dist/entries/audit-log.d.ts +1 -0
- package/dist/entries/audit-log.js +1 -0
- package/dist/entries/command-palette.d.ts +1 -0
- package/dist/entries/command-palette.js +1 -0
- package/dist/entries/compare-view.d.ts +1 -0
- package/dist/entries/compare-view.js +1 -0
- package/dist/entries/diff-viewer.d.ts +1 -0
- package/dist/entries/diff-viewer.js +1 -0
- package/dist/entries/due-badge.d.ts +1 -0
- package/dist/entries/due-badge.js +1 -0
- package/dist/entries/form-wizard.d.ts +1 -0
- package/dist/entries/form-wizard.js +1 -0
- package/dist/entries/lineage-graph.d.ts +1 -0
- package/dist/entries/lineage-graph.js +1 -0
- package/dist/entries/notification-bell.d.ts +1 -0
- package/dist/entries/notification-bell.js +1 -0
- package/dist/entries/notification-inbox.d.ts +1 -0
- package/dist/entries/notification-inbox.js +1 -0
- package/dist/entries/provenance-strip.d.ts +1 -0
- package/dist/entries/provenance-strip.js +1 -0
- package/dist/entries/run-trace.d.ts +1 -0
- package/dist/entries/run-trace.js +1 -0
- package/dist/entries/shortcuts-overlay.d.ts +1 -0
- package/dist/entries/shortcuts-overlay.js +1 -0
- package/dist/entries/signature-ceremony.d.ts +1 -0
- package/dist/entries/signature-ceremony.js +1 -0
- package/dist/entries/stage-path.d.ts +1 -0
- package/dist/entries/stage-path.js +1 -0
- package/dist/entries/timeline.d.ts +1 -0
- package/dist/entries/timeline.js +1 -0
- package/dist/entries/version-graph.d.ts +1 -0
- package/dist/entries/version-graph.js +1 -0
- package/dist/entries/version-list.d.ts +1 -0
- package/dist/entries/version-list.js +1 -0
- package/dist/entries/wizard-summary.d.ts +1 -0
- package/dist/entries/wizard-summary.js +1 -0
- package/dist/entries/work-queue.d.ts +1 -0
- package/dist/entries/work-queue.js +1 -0
- package/dist/entries/workload-board.d.ts +1 -0
- package/dist/entries/workload-board.js +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +21 -0
- package/dist/patterns/agent-chat/agent-chat.d.ts +75 -0
- package/dist/patterns/agent-chat/agent-chat.js +820 -0
- package/dist/patterns/agent-chat/controller.d.ts +27 -0
- package/dist/patterns/agent-chat/controller.js +182 -0
- package/dist/patterns/agent-chat/index.d.ts +3 -0
- package/dist/patterns/agent-chat/index.js +6 -0
- package/dist/patterns/agent-chat/types.d.ts +109 -0
- package/dist/patterns/agent-chat/types.js +1 -0
- package/dist/patterns/audit/activity-density.d.ts +68 -0
- package/dist/patterns/audit/activity-density.js +428 -0
- package/dist/patterns/audit/audit-log.d.ts +81 -0
- package/dist/patterns/audit/audit-log.js +794 -0
- package/dist/patterns/audit/index.d.ts +3 -0
- package/dist/patterns/audit/index.js +7 -0
- package/dist/patterns/audit/shared.d.ts +10 -0
- package/dist/patterns/audit/shared.js +50 -0
- package/dist/patterns/audit/types.d.ts +143 -0
- package/dist/patterns/audit/types.js +327 -0
- package/dist/patterns/content-explorer/actions/controller.js +12 -2
- package/dist/patterns/content-explorer/box-transport.js +117 -25
- package/dist/patterns/content-explorer/content-explorer.d.ts +13 -1
- package/dist/patterns/content-explorer/content-explorer.js +36 -3
- package/dist/patterns/content-explorer/contracts.d.ts +24 -1
- package/dist/patterns/content-explorer/contracts.js +68 -11
- package/dist/patterns/content-explorer/controller.d.ts +16 -1
- package/dist/patterns/content-explorer/controller.js +130 -4
- package/dist/patterns/content-explorer/navigation/controller.js +11 -9
- package/dist/patterns/content-explorer/types.d.ts +58 -1
- package/dist/patterns/content-picker/content-picker.d.ts +71 -0
- package/dist/patterns/content-picker/content-picker.js +747 -0
- package/dist/patterns/content-picker/controller.d.ts +38 -0
- package/dist/patterns/content-picker/controller.js +141 -0
- package/dist/patterns/content-picker/index.d.ts +3 -0
- package/dist/patterns/content-picker/index.js +6 -0
- package/dist/patterns/content-picker/types.d.ts +52 -0
- package/dist/patterns/content-picker/types.js +26 -0
- package/dist/patterns/content-sidebar/content-sidebar.d.ts +43 -0
- package/dist/patterns/content-sidebar/content-sidebar.js +293 -0
- package/dist/patterns/content-sidebar/index.d.ts +2 -0
- package/dist/patterns/content-sidebar/index.js +4 -0
- package/dist/patterns/content-sidebar/types.d.ts +18 -0
- package/dist/patterns/content-sidebar/types.js +20 -0
- package/dist/patterns/content-uploader/box-transport.d.ts +14 -0
- package/dist/patterns/content-uploader/box-transport.js +54 -0
- package/dist/patterns/content-uploader/content-uploader.d.ts +62 -0
- package/dist/patterns/content-uploader/content-uploader.js +559 -0
- package/dist/patterns/content-uploader/controller.d.ts +39 -0
- package/dist/patterns/content-uploader/controller.js +227 -0
- package/dist/patterns/content-uploader/index.d.ts +4 -0
- package/dist/patterns/content-uploader/index.js +8 -0
- package/dist/patterns/content-uploader/types.d.ts +120 -0
- package/dist/patterns/content-uploader/types.js +38 -0
- package/dist/patterns/diff/compare-view.d.ts +47 -0
- package/dist/patterns/diff/compare-view.js +276 -0
- package/dist/patterns/diff/diff-viewer.d.ts +42 -0
- package/dist/patterns/diff/diff-viewer.js +434 -0
- package/dist/patterns/diff/engine.d.ts +22 -0
- package/dist/patterns/diff/engine.js +276 -0
- package/dist/patterns/diff/index.d.ts +4 -0
- package/dist/patterns/diff/index.js +8 -0
- package/dist/patterns/diff/types.d.ts +69 -0
- package/dist/patterns/diff/types.js +24 -0
- package/dist/patterns/form-wizard/controller.d.ts +39 -0
- package/dist/patterns/form-wizard/controller.js +157 -0
- package/dist/patterns/form-wizard/form-wizard.d.ts +63 -0
- package/dist/patterns/form-wizard/form-wizard.js +468 -0
- package/dist/patterns/form-wizard/index.d.ts +4 -0
- package/dist/patterns/form-wizard/index.js +8 -0
- package/dist/patterns/form-wizard/types.d.ts +109 -0
- package/dist/patterns/form-wizard/types.js +83 -0
- package/dist/patterns/form-wizard/wizard-summary.d.ts +62 -0
- package/dist/patterns/form-wizard/wizard-summary.js +324 -0
- package/dist/patterns/internal/safe-href.d.ts +17 -0
- package/dist/patterns/internal/safe-href.js +17 -0
- package/dist/patterns/lineage/index.d.ts +3 -0
- package/dist/patterns/lineage/index.js +6 -0
- package/dist/patterns/lineage/lineage-graph.d.ts +30 -0
- package/dist/patterns/lineage/lineage-graph.js +434 -0
- package/dist/patterns/lineage/provenance-strip.d.ts +22 -0
- package/dist/patterns/lineage/provenance-strip.js +194 -0
- package/dist/patterns/lineage/types.d.ts +53 -0
- package/dist/patterns/lineage/types.js +49 -0
- package/dist/patterns/notifications/index.d.ts +3 -0
- package/dist/patterns/notifications/index.js +6 -0
- package/dist/patterns/notifications/notification-bell.d.ts +38 -0
- package/dist/patterns/notifications/notification-bell.js +198 -0
- package/dist/patterns/notifications/notification-inbox.d.ts +46 -0
- package/dist/patterns/notifications/notification-inbox.js +537 -0
- package/dist/patterns/notifications/types.d.ts +61 -0
- package/dist/patterns/notifications/types.js +108 -0
- package/dist/patterns/preview/annotation-inspector.d.ts +25 -6
- package/dist/patterns/preview/annotation-inspector.js +239 -94
- package/dist/patterns/preview/annotation-thread.d.ts +25 -5
- package/dist/patterns/preview/annotation-thread.js +233 -82
- package/dist/patterns/preview/annotation-toolbar.d.ts +20 -6
- package/dist/patterns/preview/annotation-toolbar.js +162 -96
- package/dist/patterns/preview/content-preview-adapter.d.ts +10 -1
- package/dist/patterns/preview/content-preview-adapter.js +26 -1
- package/dist/patterns/preview/preview-element.d.ts +15 -2
- package/dist/patterns/preview/preview-element.js +223 -52
- package/dist/patterns/preview/provider-adapter.d.ts +47 -0
- package/dist/patterns/preview/provider-adapter.js +17 -0
- package/dist/patterns/run/index.d.ts +2 -0
- package/dist/patterns/run/index.js +4 -0
- package/dist/patterns/run/run-trace.d.ts +40 -0
- package/dist/patterns/run/run-trace.js +441 -0
- package/dist/patterns/run/types.d.ts +66 -0
- package/dist/patterns/run/types.js +152 -0
- package/dist/patterns/signature/index.d.ts +2 -0
- package/dist/patterns/signature/index.js +4 -0
- package/dist/patterns/signature/signature-ceremony.d.ts +33 -0
- package/dist/patterns/signature/signature-ceremony.js +317 -0
- package/dist/patterns/signature/types.d.ts +68 -0
- package/dist/patterns/signature/types.js +89 -0
- package/dist/patterns/timeline/index.d.ts +2 -0
- package/dist/patterns/timeline/index.js +4 -0
- package/dist/patterns/timeline/timeline.d.ts +27 -0
- package/dist/patterns/timeline/timeline.js +430 -0
- package/dist/patterns/timeline/types.d.ts +36 -0
- package/dist/patterns/timeline/types.js +27 -0
- package/dist/patterns/versions/graph-layout.d.ts +43 -0
- package/dist/patterns/versions/graph-layout.js +159 -0
- package/dist/patterns/versions/index.d.ts +4 -0
- package/dist/patterns/versions/index.js +8 -0
- package/dist/patterns/versions/shared.d.ts +4 -0
- package/dist/patterns/versions/shared.js +13 -0
- package/dist/patterns/versions/types.d.ts +37 -0
- package/dist/patterns/versions/types.js +39 -0
- package/dist/patterns/versions/version-graph.d.ts +36 -0
- package/dist/patterns/versions/version-graph.js +438 -0
- package/dist/patterns/versions/version-list.d.ts +39 -0
- package/dist/patterns/versions/version-list.js +407 -0
- package/dist/patterns/work-queue/controller.d.ts +26 -0
- package/dist/patterns/work-queue/controller.js +150 -0
- package/dist/patterns/work-queue/index.d.ts +4 -0
- package/dist/patterns/work-queue/index.js +7 -0
- package/dist/patterns/work-queue/types.d.ts +128 -0
- package/dist/patterns/work-queue/types.js +45 -0
- package/dist/patterns/work-queue/work-queue.d.ts +48 -0
- package/dist/patterns/work-queue/work-queue.js +534 -0
- package/dist/patterns/work-queue/workload-board.d.ts +53 -0
- package/dist/patterns/work-queue/workload-board.js +608 -0
- package/package.json +89 -5
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import { groupCommandMatches, isCommandDescriptorRecord, matchCommands, splitCommandLabel, } from "./command-types.js";
|
|
2
|
+
import { BaseElement } from "../../core/index.js";
|
|
3
|
+
import { FocusRestore, trapTabKey } from "../../foundations/a11y/index.js";
|
|
4
|
+
import { boeOverlay, boePanel, boeRadius } from "../../foundations/geometry/index.js";
|
|
5
|
+
import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
|
|
6
|
+
const DEFAULT_TAG_NAME = "box-command-palette";
|
|
7
|
+
const escapeHtml = (value) => value
|
|
8
|
+
.replaceAll("&", "&")
|
|
9
|
+
.replaceAll("<", "<")
|
|
10
|
+
.replaceAll(">", ">")
|
|
11
|
+
.replaceAll('"', """)
|
|
12
|
+
.replaceAll("'", "'");
|
|
13
|
+
const elementStyles = `
|
|
14
|
+
[hidden] {
|
|
15
|
+
display: none !important;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
:host {
|
|
19
|
+
display: contents;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
[part="backdrop"] {
|
|
23
|
+
position: fixed;
|
|
24
|
+
inset: 0;
|
|
25
|
+
display: flex;
|
|
26
|
+
justify-content: center;
|
|
27
|
+
align-items: start;
|
|
28
|
+
padding-block-start: 12vh;
|
|
29
|
+
background: ${boeOverlay.modalBackdrop};
|
|
30
|
+
z-index: 1000;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
[part="palette"] {
|
|
34
|
+
inline-size: min(40rem, calc(100vw - 2rem));
|
|
35
|
+
max-block-size: 70vh;
|
|
36
|
+
display: grid;
|
|
37
|
+
grid-template-rows: auto 1fr auto;
|
|
38
|
+
border-radius: ${boeOverlay.modalRadius};
|
|
39
|
+
border: ${boePanel.border};
|
|
40
|
+
background: ${boePanel.background};
|
|
41
|
+
box-shadow: ${boeOverlay.shadow};
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
[part="search-row"] {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: 0.5rem;
|
|
49
|
+
padding: 0.7rem 0.9rem;
|
|
50
|
+
border-block-end: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 70%, transparent);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
[part="search-icon"] {
|
|
54
|
+
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
[part="search"] {
|
|
58
|
+
flex: 1;
|
|
59
|
+
appearance: none;
|
|
60
|
+
border: none;
|
|
61
|
+
background: none;
|
|
62
|
+
font: inherit;
|
|
63
|
+
font-size: 1rem;
|
|
64
|
+
color: var(--boe-token-text-text, #1f1e1b);
|
|
65
|
+
outline: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
[part="search"]::placeholder {
|
|
69
|
+
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
[part="results"] {
|
|
73
|
+
margin: 0;
|
|
74
|
+
padding: 0.35rem;
|
|
75
|
+
list-style: none;
|
|
76
|
+
overflow-y: auto;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
[part="group-label"] {
|
|
80
|
+
padding: 0.45rem 0.6rem 0.25rem;
|
|
81
|
+
font-size: 0.7rem;
|
|
82
|
+
font-weight: 700;
|
|
83
|
+
letter-spacing: 0.08em;
|
|
84
|
+
text-transform: uppercase;
|
|
85
|
+
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
[part="option"] {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
gap: 0.6rem;
|
|
92
|
+
padding: 0.5rem 0.6rem;
|
|
93
|
+
border-radius: ${boeOverlay.itemRadius};
|
|
94
|
+
cursor: pointer;
|
|
95
|
+
transition: background ${boeMotionDuration.interactive} ${boeMotionEasing.standard};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
[part="option"][aria-selected="true"] {
|
|
99
|
+
background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 12%, transparent);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
[part="option"][aria-disabled="true"] {
|
|
103
|
+
opacity: 0.5;
|
|
104
|
+
cursor: not-allowed;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
[part="option-text"] {
|
|
108
|
+
display: grid;
|
|
109
|
+
gap: 0.1rem;
|
|
110
|
+
min-inline-size: 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
[part="option-label"] {
|
|
114
|
+
color: var(--boe-token-text-text, #1f1e1b);
|
|
115
|
+
overflow: hidden;
|
|
116
|
+
text-overflow: ellipsis;
|
|
117
|
+
white-space: nowrap;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
[part="match"] {
|
|
121
|
+
font-weight: 700;
|
|
122
|
+
color: var(--boe-token-surface-surface-brand, #0061d5);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
[part="option-description"] {
|
|
126
|
+
font-size: 0.78rem;
|
|
127
|
+
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
128
|
+
overflow: hidden;
|
|
129
|
+
text-overflow: ellipsis;
|
|
130
|
+
white-space: nowrap;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
[part="shortcut"] {
|
|
134
|
+
margin-inline-start: auto;
|
|
135
|
+
padding: 0.1rem 0.35rem;
|
|
136
|
+
border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 90%, transparent);
|
|
137
|
+
border-radius: ${boeRadius.size};
|
|
138
|
+
font-family: ui-monospace, monospace;
|
|
139
|
+
font-size: 0.72rem;
|
|
140
|
+
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
141
|
+
white-space: nowrap;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
[part="empty"] {
|
|
145
|
+
padding: 1.4rem 0.9rem;
|
|
146
|
+
text-align: center;
|
|
147
|
+
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
[part="footer"] {
|
|
151
|
+
display: flex;
|
|
152
|
+
gap: 0.9rem;
|
|
153
|
+
padding: 0.45rem 0.9rem;
|
|
154
|
+
border-block-start: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 70%, transparent);
|
|
155
|
+
font-size: 0.72rem;
|
|
156
|
+
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
[part="footer-key"] {
|
|
160
|
+
font-family: ui-monospace, monospace;
|
|
161
|
+
font-weight: 700;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@media (prefers-reduced-motion: reduce) {
|
|
165
|
+
[part="option"] {
|
|
166
|
+
transition: none;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
`;
|
|
170
|
+
/**
|
|
171
|
+
* Keyboard-first action launcher (CLM gap 4) over the existing overlay and
|
|
172
|
+
* listbox machinery.
|
|
173
|
+
*
|
|
174
|
+
* Follows the ARIA combobox-with-listbox pattern: focus never leaves the
|
|
175
|
+
* search input, so typing and navigating are the same gesture. The active
|
|
176
|
+
* option is communicated with `aria-activedescendant` rather than by moving
|
|
177
|
+
* focus, which is what lets Up/Down browse results while the query stays
|
|
178
|
+
* editable.
|
|
179
|
+
*
|
|
180
|
+
* The host owns what commands mean — the palette only reports the choice.
|
|
181
|
+
*/
|
|
182
|
+
export class CommandPalette extends BaseElement {
|
|
183
|
+
static tagName = DEFAULT_TAG_NAME;
|
|
184
|
+
static get observedAttributes() {
|
|
185
|
+
return ["commands", "hide-disabled", "hotkey", "open", "placeholder"];
|
|
186
|
+
}
|
|
187
|
+
hostEl;
|
|
188
|
+
focusRestore = new FocusRestore();
|
|
189
|
+
openValue = false;
|
|
190
|
+
wasOpen = false;
|
|
191
|
+
query = "";
|
|
192
|
+
activeIndex = 0;
|
|
193
|
+
/** Flattened, ranked matches — the index space the keyboard walks. */
|
|
194
|
+
rankedMatches = [];
|
|
195
|
+
commandsRaw = null;
|
|
196
|
+
commandsCache = [];
|
|
197
|
+
recentIdsValue = [];
|
|
198
|
+
onDocumentKeydown = (event) => {
|
|
199
|
+
const hotkey = this.hotkey;
|
|
200
|
+
if (!hotkey || this.openValue) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// `mod` means Cmd on Apple platforms and Ctrl elsewhere — the same
|
|
204
|
+
// physical gesture, which is what "⌘K or Ctrl+K" actually means.
|
|
205
|
+
const parts = hotkey.toLowerCase().split("+").map(part => part.trim());
|
|
206
|
+
const key = parts[parts.length - 1] ?? "";
|
|
207
|
+
const wantsMod = parts.includes("mod");
|
|
208
|
+
const wantsShift = parts.includes("shift");
|
|
209
|
+
const wantsAlt = parts.includes("alt");
|
|
210
|
+
const modPressed = event.metaKey || event.ctrlKey;
|
|
211
|
+
if (event.key.toLowerCase() === key &&
|
|
212
|
+
(!wantsMod || modPressed) &&
|
|
213
|
+
(wantsMod || !modPressed) &&
|
|
214
|
+
wantsShift === event.shiftKey &&
|
|
215
|
+
wantsAlt === event.altKey) {
|
|
216
|
+
event.preventDefault();
|
|
217
|
+
this.open = true;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
get open() {
|
|
221
|
+
return this.hasAttribute("open");
|
|
222
|
+
}
|
|
223
|
+
set open(value) {
|
|
224
|
+
this.toggleAttribute("open", value);
|
|
225
|
+
}
|
|
226
|
+
get placeholder() {
|
|
227
|
+
return this.getAttribute("placeholder") ?? "Type a command or search…";
|
|
228
|
+
}
|
|
229
|
+
set placeholder(value) {
|
|
230
|
+
this.setAttribute("placeholder", value);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Global shortcut that opens the palette, e.g. `mod+k`. `mod` resolves to
|
|
234
|
+
* Cmd or Ctrl per platform. Omit to let the host own the trigger.
|
|
235
|
+
*/
|
|
236
|
+
get hotkey() {
|
|
237
|
+
return this.getAttribute("hotkey") ?? "";
|
|
238
|
+
}
|
|
239
|
+
set hotkey(value) {
|
|
240
|
+
this.setAttribute("hotkey", value);
|
|
241
|
+
}
|
|
242
|
+
get hideDisabled() {
|
|
243
|
+
return this.hasAttribute("hide-disabled");
|
|
244
|
+
}
|
|
245
|
+
set hideDisabled(value) {
|
|
246
|
+
this.toggleAttribute("hide-disabled", value);
|
|
247
|
+
}
|
|
248
|
+
/** Command catalogue; JSON payloads are validated per record. */
|
|
249
|
+
get commands() {
|
|
250
|
+
const raw = this.getAttribute("commands");
|
|
251
|
+
if (!raw) {
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
if (raw !== this.commandsRaw) {
|
|
255
|
+
this.commandsRaw = raw;
|
|
256
|
+
try {
|
|
257
|
+
const parsed = JSON.parse(raw);
|
|
258
|
+
this.commandsCache =
|
|
259
|
+
Array.isArray(parsed) && parsed.every(isCommandDescriptorRecord)
|
|
260
|
+
? parsed
|
|
261
|
+
: [];
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
this.commandsCache = [];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return [...this.commandsCache];
|
|
268
|
+
}
|
|
269
|
+
set commands(value) {
|
|
270
|
+
if (value.length) {
|
|
271
|
+
this.setAttribute("commands", JSON.stringify(value));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
this.removeAttribute("commands");
|
|
275
|
+
}
|
|
276
|
+
/** Most recently run command ids, most recent first; boosts ranking. */
|
|
277
|
+
get recentIds() {
|
|
278
|
+
return [...this.recentIdsValue];
|
|
279
|
+
}
|
|
280
|
+
set recentIds(value) {
|
|
281
|
+
this.recentIdsValue = [...value];
|
|
282
|
+
if (this.isRendered) {
|
|
283
|
+
this.update();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/** The ranked matches currently on screen. */
|
|
287
|
+
get visibleCommands() {
|
|
288
|
+
return this.rankedMatches.map(match => match.command);
|
|
289
|
+
}
|
|
290
|
+
show() {
|
|
291
|
+
this.open = true;
|
|
292
|
+
}
|
|
293
|
+
hide() {
|
|
294
|
+
this.open = false;
|
|
295
|
+
}
|
|
296
|
+
connectedCallback() {
|
|
297
|
+
super.connectedCallback();
|
|
298
|
+
document.addEventListener("keydown", this.onDocumentKeydown);
|
|
299
|
+
}
|
|
300
|
+
disconnectedCallback() {
|
|
301
|
+
document.removeEventListener("keydown", this.onDocumentKeydown);
|
|
302
|
+
}
|
|
303
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
304
|
+
if (name === "open" && oldValue !== newValue && this.hasAttribute("open")) {
|
|
305
|
+
// Every opening starts from a clean query — a palette that reopens
|
|
306
|
+
// holding the last search is a nuisance, not a convenience.
|
|
307
|
+
this.query = "";
|
|
308
|
+
this.activeIndex = 0;
|
|
309
|
+
}
|
|
310
|
+
super.attributeChangedCallback(name, oldValue, newValue);
|
|
311
|
+
}
|
|
312
|
+
runActive() {
|
|
313
|
+
const match = this.rankedMatches[this.activeIndex];
|
|
314
|
+
if (!match || match.command.disabled) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
this.dispatchEvent(new CustomEvent("command-selected", {
|
|
318
|
+
bubbles: true,
|
|
319
|
+
composed: true,
|
|
320
|
+
detail: { command: match.command },
|
|
321
|
+
}));
|
|
322
|
+
this.open = false;
|
|
323
|
+
}
|
|
324
|
+
moveActive(delta) {
|
|
325
|
+
if (this.rankedMatches.length === 0) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
// Wrap: from the last result, Down returns to the first. A palette is a
|
|
329
|
+
// short list, and dead-ending at the bottom just costs a keystroke.
|
|
330
|
+
const next = (this.activeIndex + delta + this.rankedMatches.length) % this.rankedMatches.length;
|
|
331
|
+
this.activeIndex = next;
|
|
332
|
+
this.update();
|
|
333
|
+
this.scrollActiveIntoView();
|
|
334
|
+
}
|
|
335
|
+
scrollActiveIntoView() {
|
|
336
|
+
const active = this.hostEl.querySelector('[part="option"][aria-selected="true"]');
|
|
337
|
+
// Not every DOM provides scrollIntoView (jsdom, SSR shims). Keeping the
|
|
338
|
+
// active option in view is an enhancement — never a reason to throw and
|
|
339
|
+
// take the whole keypress down with it.
|
|
340
|
+
active?.scrollIntoView?.({ block: "nearest" });
|
|
341
|
+
}
|
|
342
|
+
optionHtml(match, index) {
|
|
343
|
+
const { command } = match;
|
|
344
|
+
const label = splitCommandLabel(command.label, match.ranges)
|
|
345
|
+
.map(part => part.match
|
|
346
|
+
? `<span part="match">${escapeHtml(part.text)}</span>`
|
|
347
|
+
: escapeHtml(part.text))
|
|
348
|
+
.join("");
|
|
349
|
+
return `
|
|
350
|
+
<li
|
|
351
|
+
part="option"
|
|
352
|
+
role="option"
|
|
353
|
+
id="command-option-${String(index)}"
|
|
354
|
+
data-command-id="${escapeHtml(command.id)}"
|
|
355
|
+
data-index="${String(index)}"
|
|
356
|
+
aria-selected="${index === this.activeIndex ? "true" : "false"}"
|
|
357
|
+
${command.disabled ? 'aria-disabled="true"' : ""}
|
|
358
|
+
>
|
|
359
|
+
<span part="option-text">
|
|
360
|
+
<span part="option-label">${label}</span>
|
|
361
|
+
${command.description ? `<span part="option-description">${escapeHtml(command.description)}</span>` : ""}
|
|
362
|
+
</span>
|
|
363
|
+
${command.shortcut ? `<span part="shortcut">${escapeHtml(command.shortcut)}</span>` : ""}
|
|
364
|
+
</li>
|
|
365
|
+
`;
|
|
366
|
+
}
|
|
367
|
+
renderTemplate() {
|
|
368
|
+
if (!this.shadowRoot) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
this.shadowRoot.innerHTML = `<style>${elementStyles}</style><div part="host"></div>`;
|
|
372
|
+
this.hostEl = this.shadowRoot.querySelector('[part="host"]');
|
|
373
|
+
}
|
|
374
|
+
setupListeners() {
|
|
375
|
+
this.hostEl.addEventListener("pointerdown", event => {
|
|
376
|
+
const target = event.target;
|
|
377
|
+
if (target.closest('[part="palette"]')) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (target.closest('[part="backdrop"]')) {
|
|
381
|
+
this.dispatchEvent(new CustomEvent("dismissed", { bubbles: true, composed: true }));
|
|
382
|
+
this.open = false;
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
this.hostEl.addEventListener("click", event => {
|
|
386
|
+
const option = event.target.closest('[part="option"]');
|
|
387
|
+
if (!option || !this.hostEl.contains(option)) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
this.activeIndex = Number(option.getAttribute("data-index") ?? "0");
|
|
391
|
+
this.runActive();
|
|
392
|
+
});
|
|
393
|
+
this.hostEl.addEventListener("input", event => {
|
|
394
|
+
const input = event.target;
|
|
395
|
+
if (input.getAttribute("part") !== "search") {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
this.query = input.value;
|
|
399
|
+
this.activeIndex = 0;
|
|
400
|
+
this.update();
|
|
401
|
+
});
|
|
402
|
+
this.hostEl.addEventListener("keydown", event => {
|
|
403
|
+
const keyboardEvent = event;
|
|
404
|
+
const palette = this.hostEl.querySelector('[part="palette"]');
|
|
405
|
+
if (!palette) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
switch (keyboardEvent.key) {
|
|
409
|
+
case "Escape":
|
|
410
|
+
keyboardEvent.preventDefault();
|
|
411
|
+
this.dispatchEvent(new CustomEvent("dismissed", { bubbles: true, composed: true }));
|
|
412
|
+
this.open = false;
|
|
413
|
+
return;
|
|
414
|
+
case "ArrowDown":
|
|
415
|
+
keyboardEvent.preventDefault();
|
|
416
|
+
this.moveActive(1);
|
|
417
|
+
return;
|
|
418
|
+
case "ArrowUp":
|
|
419
|
+
keyboardEvent.preventDefault();
|
|
420
|
+
this.moveActive(-1);
|
|
421
|
+
return;
|
|
422
|
+
case "Home":
|
|
423
|
+
keyboardEvent.preventDefault();
|
|
424
|
+
this.activeIndex = 0;
|
|
425
|
+
this.update();
|
|
426
|
+
this.scrollActiveIntoView();
|
|
427
|
+
return;
|
|
428
|
+
case "End":
|
|
429
|
+
keyboardEvent.preventDefault();
|
|
430
|
+
this.activeIndex = Math.max(0, this.rankedMatches.length - 1);
|
|
431
|
+
this.update();
|
|
432
|
+
this.scrollActiveIntoView();
|
|
433
|
+
return;
|
|
434
|
+
case "Enter":
|
|
435
|
+
keyboardEvent.preventDefault();
|
|
436
|
+
this.runActive();
|
|
437
|
+
return;
|
|
438
|
+
case "Tab":
|
|
439
|
+
trapTabKey(keyboardEvent, palette);
|
|
440
|
+
return;
|
|
441
|
+
default:
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
update() {
|
|
446
|
+
if (!this.hostEl) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
this.openValue = this.open;
|
|
450
|
+
if (!this.openValue) {
|
|
451
|
+
const wasOpen = this.wasOpen;
|
|
452
|
+
this.hostEl.innerHTML = "";
|
|
453
|
+
this.rankedMatches = [];
|
|
454
|
+
this.wasOpen = false;
|
|
455
|
+
if (wasOpen) {
|
|
456
|
+
this.focusRestore.restore();
|
|
457
|
+
}
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const justOpened = !this.wasOpen;
|
|
461
|
+
this.wasOpen = true;
|
|
462
|
+
if (justOpened) {
|
|
463
|
+
this.focusRestore.capture();
|
|
464
|
+
}
|
|
465
|
+
const groups = groupCommandMatches(matchCommands(this.commands, this.query, {
|
|
466
|
+
recentIds: this.recentIdsValue,
|
|
467
|
+
hideDisabled: this.hideDisabled,
|
|
468
|
+
}));
|
|
469
|
+
// Flatten in *rendered* order, not rank order. Grouping can reorder
|
|
470
|
+
// matches relative to their rank — the ungrouped section always trails —
|
|
471
|
+
// so indexing the keyboard off the rank order would highlight one option
|
|
472
|
+
// while Enter ran a different one.
|
|
473
|
+
this.rankedMatches = groups.flatMap(group => group.matches);
|
|
474
|
+
if (this.activeIndex >= this.rankedMatches.length) {
|
|
475
|
+
this.activeIndex = Math.max(0, this.rankedMatches.length - 1);
|
|
476
|
+
}
|
|
477
|
+
// One flat index space across groups, so Up/Down crosses section
|
|
478
|
+
// headings the way a reader expects.
|
|
479
|
+
let index = 0;
|
|
480
|
+
const sections = groups
|
|
481
|
+
.map(group => {
|
|
482
|
+
const options = group.matches.map(match => this.optionHtml(match, index++)).join("");
|
|
483
|
+
return `<li part="group" role="presentation"><span part="group-label" role="presentation">${escapeHtml(group.label)}</span><ul part="group-options" role="group" aria-label="${escapeHtml(group.label)}">${options}</ul></li>`;
|
|
484
|
+
})
|
|
485
|
+
.join("");
|
|
486
|
+
const activeId = this.rankedMatches.length > 0 ? `command-option-${String(this.activeIndex)}` : "";
|
|
487
|
+
if (justOpened || !this.hostEl.querySelector('[part="palette"]')) {
|
|
488
|
+
this.hostEl.innerHTML = `
|
|
489
|
+
<div part="backdrop">
|
|
490
|
+
<div part="palette" role="dialog" aria-modal="true" aria-label="Command palette">
|
|
491
|
+
<div part="search-row">
|
|
492
|
+
<span part="search-icon" aria-hidden="true">⌕</span>
|
|
493
|
+
<input
|
|
494
|
+
part="search"
|
|
495
|
+
type="text"
|
|
496
|
+
role="combobox"
|
|
497
|
+
aria-expanded="true"
|
|
498
|
+
aria-controls="command-results"
|
|
499
|
+
aria-autocomplete="list"
|
|
500
|
+
aria-label="${escapeHtml(this.placeholder)}"
|
|
501
|
+
placeholder="${escapeHtml(this.placeholder)}"
|
|
502
|
+
autocomplete="off"
|
|
503
|
+
spellcheck="false"
|
|
504
|
+
>
|
|
505
|
+
</div>
|
|
506
|
+
<ul part="results" id="command-results" role="listbox" aria-label="Commands"></ul>
|
|
507
|
+
<div part="empty" hidden>No matching commands.</div>
|
|
508
|
+
<div part="footer">
|
|
509
|
+
<span><span part="footer-key">↑↓</span> navigate</span>
|
|
510
|
+
<span><span part="footer-key">⏎</span> run</span>
|
|
511
|
+
<span><span part="footer-key">esc</span> close</span>
|
|
512
|
+
</div>
|
|
513
|
+
</div>
|
|
514
|
+
</div>
|
|
515
|
+
`;
|
|
516
|
+
}
|
|
517
|
+
const input = this.hostEl.querySelector('[part="search"]');
|
|
518
|
+
const results = this.hostEl.querySelector('[part="results"]');
|
|
519
|
+
const empty = this.hostEl.querySelector('[part="empty"]');
|
|
520
|
+
results.innerHTML = sections;
|
|
521
|
+
results.hidden = this.rankedMatches.length === 0;
|
|
522
|
+
empty.hidden = this.rankedMatches.length > 0;
|
|
523
|
+
if (input.value !== this.query) {
|
|
524
|
+
input.value = this.query;
|
|
525
|
+
}
|
|
526
|
+
if (activeId) {
|
|
527
|
+
input.setAttribute("aria-activedescendant", activeId);
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
input.removeAttribute("aria-activedescendant");
|
|
531
|
+
}
|
|
532
|
+
if (justOpened) {
|
|
533
|
+
input.focus();
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
CommandPalette.register();
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command model and the pure match/rank/group engine behind
|
|
3
|
+
* `box-command-palette`. DOM-free and deterministic, so ranking can be
|
|
4
|
+
* tested and reused (a host can drive its own launcher from the same
|
|
5
|
+
* functions).
|
|
6
|
+
*/
|
|
7
|
+
export interface CommandDescriptor {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
/** Secondary line: what the command does, or where it acts. */
|
|
11
|
+
description?: string;
|
|
12
|
+
/** Section heading. Commands without one land in a trailing group. */
|
|
13
|
+
group?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Extra terms that should match but are not shown — synonyms, the old
|
|
16
|
+
* name for a renamed action, the entity a command acts on.
|
|
17
|
+
*/
|
|
18
|
+
keywords?: string[];
|
|
19
|
+
/** Rendered hint such as `⌘K`; purely presentational. */
|
|
20
|
+
shortcut?: string;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface CommandMatch {
|
|
24
|
+
command: CommandDescriptor;
|
|
25
|
+
/** Higher is better. */
|
|
26
|
+
score: number;
|
|
27
|
+
/** Indices into `label` that matched, for highlighting. Empty on an unfiltered list. */
|
|
28
|
+
ranges: number[];
|
|
29
|
+
}
|
|
30
|
+
export interface CommandGroup {
|
|
31
|
+
/** Empty string for the ungrouped trailing section. */
|
|
32
|
+
key: string;
|
|
33
|
+
label: string;
|
|
34
|
+
matches: CommandMatch[];
|
|
35
|
+
}
|
|
36
|
+
export declare const COMMAND_UNGROUPED_KEY = "";
|
|
37
|
+
export declare const COMMAND_UNGROUPED_LABEL = "Other";
|
|
38
|
+
export interface MatchCommandsOptions {
|
|
39
|
+
/** Command ids used most recently, most recent first. Boosts ranking. */
|
|
40
|
+
recentIds?: readonly string[];
|
|
41
|
+
/** Drop disabled commands entirely instead of ranking them last. */
|
|
42
|
+
hideDisabled?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Rank commands against a query.
|
|
46
|
+
*
|
|
47
|
+
* An empty query returns everything in recent-first then input order, which
|
|
48
|
+
* is what a palette should show when it opens. Ties are broken by input order
|
|
49
|
+
* (the sort is stable), never by object identity, so the same catalogue always
|
|
50
|
+
* renders the same way.
|
|
51
|
+
*/
|
|
52
|
+
export declare const matchCommands: (commands: readonly CommandDescriptor[], query: string, options?: MatchCommandsOptions) => CommandMatch[];
|
|
53
|
+
/**
|
|
54
|
+
* Bucket ranked matches into sections, preserving rank order both between
|
|
55
|
+
* groups (a group leads where its best match ranks) and within them.
|
|
56
|
+
* Ungrouped commands trail.
|
|
57
|
+
*/
|
|
58
|
+
export declare const groupCommandMatches: (matches: readonly CommandMatch[]) => CommandGroup[];
|
|
59
|
+
export interface ShortcutGroup {
|
|
60
|
+
key: string;
|
|
61
|
+
label: string;
|
|
62
|
+
commands: CommandDescriptor[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The commands that declare a keyboard shortcut, grouped for a shortcuts
|
|
66
|
+
* sheet. Takes the same catalogue the palette does, so one list drives both
|
|
67
|
+
* surfaces and a shortcut can never be documented but unreachable — or
|
|
68
|
+
* reachable but undocumented.
|
|
69
|
+
*
|
|
70
|
+
* Groups keep catalogue order, with the ungrouped section trailing, matching
|
|
71
|
+
* how the palette sections its results.
|
|
72
|
+
*/
|
|
73
|
+
export declare const groupShortcutCommands: (commands: readonly CommandDescriptor[]) => ShortcutGroup[];
|
|
74
|
+
/**
|
|
75
|
+
* Split a shortcut string into its keys: `mod+shift+k` → `["mod","shift","k"]`.
|
|
76
|
+
* Rendering each key separately is what lets a sheet show them as `<kbd>`
|
|
77
|
+
* elements rather than one opaque run of text.
|
|
78
|
+
*
|
|
79
|
+
* `+` is both the separator and a real key — `mod++` is how you write the zoom
|
|
80
|
+
* shortcut. A `+` with no key pending in front of it is the key itself, so
|
|
81
|
+
* `mod++` and `ctrl + +` both give `["mod", "+"]` rather than dropping it.
|
|
82
|
+
*/
|
|
83
|
+
export declare const splitShortcutKeys: (shortcut: string) => string[];
|
|
84
|
+
/** Split a label into highlighted and plain runs for rendering. */
|
|
85
|
+
export declare const splitCommandLabel: (label: string, ranges: readonly number[]) => Array<{
|
|
86
|
+
text: string;
|
|
87
|
+
match: boolean;
|
|
88
|
+
}>;
|
|
89
|
+
/** Attribute payloads are author input — validate every record. */
|
|
90
|
+
export declare const isCommandDescriptorRecord: (value: unknown) => value is CommandDescriptor;
|