@tutti-os/workbench-launchpad 0.0.27

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/dist/index.js ADDED
@@ -0,0 +1,816 @@
1
+ // src/WorkbenchLaunchpadOverlay.tsx
2
+ import {
3
+ useCallback,
4
+ useEffect,
5
+ useLayoutEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState
9
+ } from "react";
10
+ import {
11
+ CloseIcon,
12
+ NavApplicationsFilledIcon,
13
+ SearchIcon
14
+ } from "@tutti-os/ui-system";
15
+
16
+ // src/launchpadModel.ts
17
+ var workbenchLaunchpadDockActionId = "open-launchpad";
18
+ var workbenchLaunchpadDockEntryId = "workspace-launchpad";
19
+ var launchpadPreviewIconCount = 4;
20
+ var launchpadMinColumns = 2;
21
+ var launchpadMaxColumns = 7;
22
+ var launchpadMinRows = 1;
23
+ var launchpadMaxRows = 5;
24
+ var launchpadTileWidth = 136;
25
+ var launchpadTileHeight = 138;
26
+ function buildWorkbenchLaunchpadItems(input) {
27
+ const nodeItems = (input.nodeDescriptors ?? []).map((node) => ({
28
+ disabledReason: node.disabledReason,
29
+ dockEntryId: node.dockEntryId,
30
+ iconUrl: node.iconUrl,
31
+ id: `node:${node.id}`,
32
+ kind: "node",
33
+ label: node.label,
34
+ launchEnabled: node.launchEnabled ?? true,
35
+ typeId: node.typeId
36
+ }));
37
+ const pinnedNodeItems = nodeItems.slice(0, 2);
38
+ const remainingNodeItems = nodeItems.slice(2);
39
+ return [
40
+ ...pinnedNodeItems,
41
+ ...(input.apps ?? []).map((app) => ({
42
+ appId: app.appId,
43
+ disabledReason: app.disabledReason,
44
+ iconUrl: app.iconUrl,
45
+ id: app.id ?? `app:${app.appId}`,
46
+ kind: "app",
47
+ label: app.label,
48
+ launchEnabled: app.launchEnabled
49
+ })),
50
+ ...remainingNodeItems,
51
+ ...(input.agentDescriptors ?? []).map((agent) => ({
52
+ action: agent.action,
53
+ actions: agent.actions,
54
+ comingSoon: agent.comingSoon,
55
+ disabledReason: agent.disabledReason,
56
+ iconUrl: agent.iconUrl,
57
+ id: agent.id ?? `agent:${agent.provider}`,
58
+ kind: "agent",
59
+ label: agent.label,
60
+ launchEnabled: agent.launchEnabled,
61
+ provider: agent.provider,
62
+ reason: agent.reason
63
+ }))
64
+ ];
65
+ }
66
+ function filterWorkbenchLaunchpadItems(items, query) {
67
+ const normalizedQuery = normalizeLaunchpadSearchText(query);
68
+ if (!normalizedQuery) {
69
+ return [...items];
70
+ }
71
+ return items.filter(
72
+ (item) => normalizeLaunchpadSearchText(item.label).includes(normalizedQuery)
73
+ );
74
+ }
75
+ function paginateWorkbenchLaunchpadItems(items, input) {
76
+ const pageSize = Math.max(1, Math.floor(input.pageSize));
77
+ const pageCount = Math.max(1, Math.ceil(items.length / pageSize));
78
+ const currentPage = clampInteger(input.page, 0, pageCount - 1);
79
+ const pageStart = currentPage * pageSize;
80
+ return {
81
+ currentPage,
82
+ pageCount,
83
+ pageItems: items.slice(pageStart, pageStart + pageSize)
84
+ };
85
+ }
86
+ function resolveWorkbenchLaunchpadGrid(input) {
87
+ const columns = clampInteger(
88
+ Math.floor(input.width / launchpadTileWidth),
89
+ launchpadMinColumns,
90
+ launchpadMaxColumns
91
+ );
92
+ const rows = clampInteger(
93
+ Math.floor(input.height / launchpadTileHeight),
94
+ launchpadMinRows,
95
+ launchpadMaxRows
96
+ );
97
+ return {
98
+ columns,
99
+ pageSize: Math.max(1, columns * rows),
100
+ rows
101
+ };
102
+ }
103
+ function resolveWorkbenchLaunchpadPreviewIconUrls(input) {
104
+ const icons = uniqueLaunchpadPreviewIconUrls([
105
+ ...input.appIcons ?? [],
106
+ ...input.nodeIconUrls ?? [],
107
+ ...input.agentIcons ?? []
108
+ ]).slice(0, launchpadPreviewIconCount);
109
+ while (icons.length < launchpadPreviewIconCount) {
110
+ icons.push(input.fallbackIconUrl);
111
+ }
112
+ return icons;
113
+ }
114
+ function uniqueLaunchpadPreviewIconUrls(iconUrls) {
115
+ const seen = /* @__PURE__ */ new Set();
116
+ const result = [];
117
+ for (const iconUrl of iconUrls) {
118
+ const normalizedIconUrl = iconUrl?.trim();
119
+ if (!normalizedIconUrl || seen.has(normalizedIconUrl)) {
120
+ continue;
121
+ }
122
+ seen.add(normalizedIconUrl);
123
+ result.push(normalizedIconUrl);
124
+ }
125
+ return result;
126
+ }
127
+ function normalizeLaunchpadSearchText(value) {
128
+ return value.trim().toLocaleLowerCase();
129
+ }
130
+ function clampInteger(value, min, max) {
131
+ if (!Number.isFinite(value)) {
132
+ return min;
133
+ }
134
+ return Math.min(Math.max(Math.floor(value), min), max);
135
+ }
136
+
137
+ // src/launchpadWheelNavigation.ts
138
+ var wheelDeltaLinePx = 16;
139
+ var wheelDeltaPagePx = 360;
140
+ var launchpadWheelNavigationThresholdPx = 64;
141
+ var launchpadWheelNavigationCooldownMs = 420;
142
+ var launchpadWheelHorizontalIntentRatio = 1.2;
143
+ function createWorkbenchLaunchpadWheelNavigationState() {
144
+ return {
145
+ accumulatedDeltaX: 0,
146
+ lastNavigationAt: 0
147
+ };
148
+ }
149
+ function resolveWorkbenchLaunchpadWheelNavigation(input) {
150
+ if (input.pageCount <= 1) {
151
+ return {
152
+ nextPageIndex: null,
153
+ shouldPreventDefault: false,
154
+ state: createWorkbenchLaunchpadWheelNavigationState()
155
+ };
156
+ }
157
+ const delta = normalizeWorkbenchLaunchpadWheelDelta(input);
158
+ const absDeltaX = Math.abs(delta.x);
159
+ const absDeltaY = Math.abs(delta.y);
160
+ const isHorizontalIntent = absDeltaX > 0 && absDeltaX >= absDeltaY * launchpadWheelHorizontalIntentRatio;
161
+ if (!isHorizontalIntent) {
162
+ return {
163
+ nextPageIndex: null,
164
+ shouldPreventDefault: false,
165
+ state: {
166
+ accumulatedDeltaX: 0,
167
+ lastNavigationAt: input.state.lastNavigationAt
168
+ }
169
+ };
170
+ }
171
+ const direction = delta.x > 0 ? 1 : -1;
172
+ const previousDirection = input.state.accumulatedDeltaX > 0 ? 1 : -1;
173
+ const accumulatedDeltaX = input.state.accumulatedDeltaX !== 0 && previousDirection === direction ? input.state.accumulatedDeltaX + delta.x : delta.x;
174
+ if (input.timestamp - input.state.lastNavigationAt < launchpadWheelNavigationCooldownMs) {
175
+ return {
176
+ nextPageIndex: null,
177
+ shouldPreventDefault: true,
178
+ state: {
179
+ accumulatedDeltaX: 0,
180
+ lastNavigationAt: input.state.lastNavigationAt
181
+ }
182
+ };
183
+ }
184
+ if (Math.abs(accumulatedDeltaX) < launchpadWheelNavigationThresholdPx) {
185
+ return {
186
+ nextPageIndex: null,
187
+ shouldPreventDefault: true,
188
+ state: {
189
+ accumulatedDeltaX,
190
+ lastNavigationAt: input.state.lastNavigationAt
191
+ }
192
+ };
193
+ }
194
+ const nextPageIndex = input.currentPage + direction;
195
+ if (nextPageIndex < 0 || nextPageIndex >= input.pageCount) {
196
+ return {
197
+ nextPageIndex: null,
198
+ shouldPreventDefault: true,
199
+ state: {
200
+ accumulatedDeltaX: 0,
201
+ lastNavigationAt: input.timestamp
202
+ }
203
+ };
204
+ }
205
+ return {
206
+ nextPageIndex,
207
+ shouldPreventDefault: true,
208
+ state: {
209
+ accumulatedDeltaX: 0,
210
+ lastNavigationAt: input.timestamp
211
+ }
212
+ };
213
+ }
214
+ function normalizeWorkbenchLaunchpadWheelDelta(input) {
215
+ const unit = input.deltaMode === 1 ? wheelDeltaLinePx : input.deltaMode === 2 ? wheelDeltaPagePx : 1;
216
+ return {
217
+ x: input.deltaX * unit,
218
+ y: input.deltaY * unit
219
+ };
220
+ }
221
+
222
+ // src/WorkbenchLaunchpadOverlay.tsx
223
+ import { jsx, jsxs } from "react/jsx-runtime";
224
+ var defaultLaunchpadGrid = {
225
+ columns: 5,
226
+ pageSize: 15,
227
+ rows: 3
228
+ };
229
+ var launchpadExitAnimationMs = 180;
230
+ var launchpadHoverPanelViewportInsetPx = 16;
231
+ function WorkbenchLaunchpadOverlay({
232
+ copy,
233
+ dockPlacement = "bottom",
234
+ getAgentActionLabel,
235
+ getAgentActionPending,
236
+ getAgentReason,
237
+ items,
238
+ onClose,
239
+ onLaunchItem,
240
+ onPageChange,
241
+ onRunAgentAction,
242
+ onSearch,
243
+ open
244
+ }) {
245
+ const [query, setQuery] = useState("");
246
+ const [pageIndex, setPageIndex] = useState(0);
247
+ const [shouldRender, setShouldRender] = useState(open);
248
+ const searchInputRef = useRef(null);
249
+ const wheelNavigationRef = useRef(
250
+ createWorkbenchLaunchpadWheelNavigationState()
251
+ );
252
+ const { grid, ref: gridViewportRef } = useLaunchpadGridMetrics(open);
253
+ const isClosing = shouldRender && !open;
254
+ const filteredItems = useMemo(
255
+ () => filterWorkbenchLaunchpadItems(items, query),
256
+ [items, query]
257
+ );
258
+ const page = useMemo(
259
+ () => paginateWorkbenchLaunchpadItems(filteredItems, {
260
+ page: pageIndex,
261
+ pageSize: grid.pageSize
262
+ }),
263
+ [filteredItems, grid.pageSize, pageIndex]
264
+ );
265
+ useEffect(() => {
266
+ if (open) {
267
+ setShouldRender(true);
268
+ return void 0;
269
+ }
270
+ if (!shouldRender) {
271
+ return void 0;
272
+ }
273
+ const timeoutId = window.setTimeout(() => {
274
+ setShouldRender(false);
275
+ }, launchpadExitAnimationMs);
276
+ return () => {
277
+ window.clearTimeout(timeoutId);
278
+ };
279
+ }, [open, shouldRender]);
280
+ useEffect(() => {
281
+ if (!open) {
282
+ wheelNavigationRef.current = createWorkbenchLaunchpadWheelNavigationState();
283
+ return;
284
+ }
285
+ setQuery("");
286
+ setPageIndex(0);
287
+ window.setTimeout(() => searchInputRef.current?.focus(), 0);
288
+ }, [open]);
289
+ useEffect(() => {
290
+ setPageIndex(0);
291
+ wheelNavigationRef.current = createWorkbenchLaunchpadWheelNavigationState();
292
+ }, [query]);
293
+ useEffect(() => {
294
+ if (!open || !onSearch) {
295
+ return void 0;
296
+ }
297
+ const queryLength = query.trim().length;
298
+ if (queryLength === 0) {
299
+ return void 0;
300
+ }
301
+ const timeoutId = window.setTimeout(() => {
302
+ onSearch({
303
+ queryLength,
304
+ resultCount: filteredItems.length
305
+ });
306
+ }, 300);
307
+ return () => {
308
+ window.clearTimeout(timeoutId);
309
+ };
310
+ }, [filteredItems.length, onSearch, open, query]);
311
+ const changePage = useCallback(
312
+ (nextPageIndex) => {
313
+ setPageIndex((currentPageIndex) => {
314
+ const clampedPageIndex = Math.max(
315
+ 0,
316
+ Math.min(nextPageIndex, page.pageCount - 1)
317
+ );
318
+ if (clampedPageIndex !== currentPageIndex) {
319
+ onPageChange?.({
320
+ pageIndex: clampedPageIndex,
321
+ totalPages: page.pageCount
322
+ });
323
+ }
324
+ return clampedPageIndex;
325
+ });
326
+ },
327
+ [onPageChange, page.pageCount]
328
+ );
329
+ useEffect(() => {
330
+ if (!open) {
331
+ return void 0;
332
+ }
333
+ const handleKeyDown = (event) => {
334
+ if (event.key === "Escape") {
335
+ event.preventDefault();
336
+ onClose();
337
+ return;
338
+ }
339
+ if (isTextInputTarget(event.target)) {
340
+ return;
341
+ }
342
+ if (event.key === "ArrowRight" && page.pageCount > 1) {
343
+ event.preventDefault();
344
+ changePage(page.currentPage + 1);
345
+ return;
346
+ }
347
+ if (event.key === "ArrowLeft" && page.pageCount > 1) {
348
+ event.preventDefault();
349
+ changePage(page.currentPage - 1);
350
+ }
351
+ };
352
+ window.addEventListener("keydown", handleKeyDown);
353
+ return () => {
354
+ window.removeEventListener("keydown", handleKeyDown);
355
+ };
356
+ }, [changePage, onClose, open, page.currentPage, page.pageCount]);
357
+ const handleOverlayClick = useCallback(
358
+ (event) => {
359
+ if (isClosing || isLaunchpadInteractiveTarget(event.target)) {
360
+ return;
361
+ }
362
+ onClose();
363
+ },
364
+ [isClosing, onClose]
365
+ );
366
+ const handleOverlayWheel = useCallback(
367
+ (event) => {
368
+ if (!open || isClosing || isLaunchpadWheelIgnoredTarget(event.target)) {
369
+ return;
370
+ }
371
+ const result = resolveWorkbenchLaunchpadWheelNavigation({
372
+ currentPage: page.currentPage,
373
+ deltaMode: event.deltaMode,
374
+ deltaX: event.deltaX,
375
+ deltaY: event.deltaY,
376
+ pageCount: page.pageCount,
377
+ state: wheelNavigationRef.current,
378
+ timestamp: event.timeStamp
379
+ });
380
+ wheelNavigationRef.current = result.state;
381
+ if (result.shouldPreventDefault) {
382
+ event.preventDefault();
383
+ }
384
+ if (result.nextPageIndex !== null) {
385
+ changePage(result.nextPageIndex);
386
+ }
387
+ },
388
+ [changePage, isClosing, open, page.currentPage, page.pageCount]
389
+ );
390
+ if (!shouldRender) {
391
+ return null;
392
+ }
393
+ return /* @__PURE__ */ jsxs(
394
+ "div",
395
+ {
396
+ "aria-label": copy.label,
397
+ "aria-modal": "false",
398
+ className: classNames(
399
+ "workspace-launchpad-overlay",
400
+ isClosing && "workspace-launchpad-overlay--closing"
401
+ ),
402
+ role: "dialog",
403
+ onClick: handleOverlayClick,
404
+ onWheel: handleOverlayWheel,
405
+ children: [
406
+ /* @__PURE__ */ jsx(
407
+ "button",
408
+ {
409
+ "aria-hidden": "true",
410
+ className: "workspace-launchpad-overlay__dismiss",
411
+ tabIndex: -1,
412
+ type: "button",
413
+ onClick: onClose
414
+ }
415
+ ),
416
+ /* @__PURE__ */ jsxs(
417
+ "div",
418
+ {
419
+ className: "workspace-launchpad-overlay__content",
420
+ "data-dock-placement": dockPlacement,
421
+ children: [
422
+ /* @__PURE__ */ jsx("div", { className: "workspace-launchpad-overlay__topbar", children: /* @__PURE__ */ jsxs("div", { className: "workspace-launchpad-search", children: [
423
+ /* @__PURE__ */ jsx(
424
+ "span",
425
+ {
426
+ "aria-hidden": "true",
427
+ className: "workspace-launchpad-search__icon",
428
+ children: /* @__PURE__ */ jsx(SearchIcon, {})
429
+ }
430
+ ),
431
+ /* @__PURE__ */ jsx(
432
+ "input",
433
+ {
434
+ ref: searchInputRef,
435
+ "aria-label": copy.searchPlaceholder,
436
+ className: "workspace-launchpad-search__input",
437
+ placeholder: copy.searchPlaceholder,
438
+ type: "search",
439
+ value: query,
440
+ onChange: (event) => setQuery(event.target.value)
441
+ }
442
+ ),
443
+ query ? /* @__PURE__ */ jsx(
444
+ "button",
445
+ {
446
+ "aria-label": copy.clearSearch,
447
+ className: "workspace-launchpad-search__clear",
448
+ type: "button",
449
+ onClick: (event) => {
450
+ event.stopPropagation();
451
+ setQuery("");
452
+ searchInputRef.current?.focus();
453
+ },
454
+ onMouseDown: (event) => {
455
+ event.preventDefault();
456
+ event.stopPropagation();
457
+ },
458
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
459
+ }
460
+ ) : null
461
+ ] }) }),
462
+ /* @__PURE__ */ jsx(
463
+ "div",
464
+ {
465
+ ref: gridViewportRef,
466
+ className: "workspace-launchpad-grid-viewport",
467
+ children: filteredItems.length === 0 ? /* @__PURE__ */ jsx("div", { className: "workspace-launchpad-empty", children: copy.empty }) : /* @__PURE__ */ jsx(
468
+ "div",
469
+ {
470
+ className: "workspace-launchpad-grid",
471
+ style: {
472
+ gridTemplateColumns: `repeat(${grid.columns}, minmax(96px, 136px))`
473
+ },
474
+ children: page.pageItems.map((item, index) => /* @__PURE__ */ jsx(
475
+ WorkbenchLaunchpadItemButton,
476
+ {
477
+ animationIndex: index,
478
+ animationRisePx: 18 + index % grid.columns * 3,
479
+ copy,
480
+ getAgentActionLabel,
481
+ getAgentReason,
482
+ isAgentActionPending: getAgentActionPending,
483
+ item,
484
+ onLaunch: onLaunchItem,
485
+ onRunAgentAction
486
+ },
487
+ item.id
488
+ ))
489
+ }
490
+ )
491
+ }
492
+ ),
493
+ page.pageCount > 1 ? /* @__PURE__ */ jsx(
494
+ "div",
495
+ {
496
+ "aria-label": copy.pages ?? "Pages",
497
+ className: "workspace-launchpad-pages",
498
+ children: Array.from({ length: page.pageCount }, (_, index) => /* @__PURE__ */ jsx(
499
+ "button",
500
+ {
501
+ "aria-label": copy.pageDot?.({
502
+ page: index + 1,
503
+ pageCount: page.pageCount
504
+ }) ?? `${index + 1} / ${page.pageCount}`,
505
+ className: "workspace-launchpad-page-dot",
506
+ "data-active": index === page.currentPage ? "true" : void 0,
507
+ type: "button",
508
+ onClick: () => changePage(index)
509
+ },
510
+ index
511
+ ))
512
+ }
513
+ ) : /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "workspace-launchpad-pages" })
514
+ ]
515
+ }
516
+ )
517
+ ]
518
+ }
519
+ );
520
+ }
521
+ function WorkbenchLaunchpadItemButton({
522
+ animationIndex,
523
+ animationRisePx,
524
+ copy,
525
+ getAgentActionLabel,
526
+ getAgentReason,
527
+ isAgentActionPending,
528
+ item,
529
+ onLaunch,
530
+ onRunAgentAction
531
+ }) {
532
+ const disabledLabel = item.disabledReason === void 0 ? null : copy.unavailableItem?.({
533
+ reason: item.disabledReason,
534
+ title: item.label
535
+ }) ?? item.disabledReason;
536
+ const [hoverPanelOpen, setHoverPanelOpen] = useState(false);
537
+ const [hoverPanelShift, setHoverPanelShift] = useState(0);
538
+ const hoverPanelRef = useRef(null);
539
+ const hoverTimerRef = useRef(null);
540
+ const agentActions = item.kind === "agent" && !item.launchEnabled ? resolveLaunchpadAgentActions(item) : [];
541
+ const agentReason = item.kind === "agent" ? getAgentReason?.(item) ?? item.reason ?? null : null;
542
+ const showComingSoonTooltip = item.kind === "agent" && item.comingSoon === true && agentReason !== null;
543
+ const showAgentHoverPanel = item.kind === "agent" && item.comingSoon !== true && !item.launchEnabled && hoverPanelOpen && (agentActions.length > 0 || agentReason !== null);
544
+ const clearHoverTimer = useCallback(() => {
545
+ if (hoverTimerRef.current === null) {
546
+ return;
547
+ }
548
+ clearTimeout(hoverTimerRef.current);
549
+ hoverTimerRef.current = null;
550
+ }, []);
551
+ useEffect(
552
+ () => () => {
553
+ clearHoverTimer();
554
+ },
555
+ [clearHoverTimer]
556
+ );
557
+ useLayoutEffect(() => {
558
+ if (!showAgentHoverPanel || typeof window === "undefined") {
559
+ setHoverPanelShift(0);
560
+ return void 0;
561
+ }
562
+ const frameId = window.requestAnimationFrame(() => {
563
+ const panel = hoverPanelRef.current;
564
+ if (!panel) {
565
+ return;
566
+ }
567
+ const rect = panel.getBoundingClientRect();
568
+ const maxRight = window.innerWidth - launchpadHoverPanelViewportInsetPx;
569
+ const nextShift = rect.left < launchpadHoverPanelViewportInsetPx ? launchpadHoverPanelViewportInsetPx - rect.left : rect.right > maxRight ? maxRight - rect.right : 0;
570
+ setHoverPanelShift(
571
+ (current) => current === nextShift ? current : nextShift
572
+ );
573
+ });
574
+ return () => {
575
+ window.cancelAnimationFrame(frameId);
576
+ };
577
+ }, [agentActions.length, agentReason, showAgentHoverPanel]);
578
+ const handlePointerEnter = useCallback(() => {
579
+ if (item.kind !== "agent" || item.launchEnabled || item.comingSoon) {
580
+ return;
581
+ }
582
+ clearHoverTimer();
583
+ hoverTimerRef.current = setTimeout(() => {
584
+ hoverTimerRef.current = null;
585
+ setHoverPanelOpen(true);
586
+ }, 500);
587
+ }, [clearHoverTimer, item]);
588
+ const handlePointerLeave = useCallback(() => {
589
+ clearHoverTimer();
590
+ setHoverPanelOpen(false);
591
+ }, [clearHoverTimer]);
592
+ const animationStyle = {
593
+ "--workspace-launchpad-item-delay": `${Math.min(animationIndex, 24) * 28}ms`,
594
+ "--workspace-launchpad-item-rise": `${animationRisePx}px`
595
+ };
596
+ const itemNode = /* @__PURE__ */ jsxs(
597
+ "div",
598
+ {
599
+ className: "workspace-launchpad-item-wrap",
600
+ style: animationStyle,
601
+ onPointerEnter: handlePointerEnter,
602
+ onPointerLeave: handlePointerLeave,
603
+ children: [
604
+ /* @__PURE__ */ jsxs(
605
+ "button",
606
+ {
607
+ "aria-label": disabledLabel ?? item.label,
608
+ className: classNames(
609
+ "workspace-launchpad-item",
610
+ !item.launchEnabled && "workspace-launchpad-item--disabled"
611
+ ),
612
+ disabled: !item.launchEnabled,
613
+ title: showComingSoonTooltip ? void 0 : disabledLabel ?? item.label,
614
+ type: "button",
615
+ onClick: () => onLaunch(item),
616
+ children: [
617
+ /* @__PURE__ */ jsx(
618
+ "span",
619
+ {
620
+ "aria-hidden": "true",
621
+ className: "workspace-launchpad-item__icon",
622
+ "data-workspace-app-icon": item.iconUrl ? "true" : void 0,
623
+ children: item.iconUrl ? /* @__PURE__ */ jsx("img", { alt: "", draggable: false, src: item.iconUrl }) : /* @__PURE__ */ jsx(NavApplicationsFilledIcon, { className: "size-10" })
624
+ }
625
+ ),
626
+ /* @__PURE__ */ jsx("span", { className: "workspace-launchpad-item__label", children: item.label })
627
+ ]
628
+ }
629
+ ),
630
+ showAgentHoverPanel ? /* @__PURE__ */ jsxs(
631
+ "div",
632
+ {
633
+ ref: hoverPanelRef,
634
+ className: "desktop-dock__hover-panel workspace-launchpad-dock-hover-panel",
635
+ role: "group",
636
+ style: {
637
+ "--workspace-launchpad-hover-panel-shift": `${Math.round(
638
+ hoverPanelShift
639
+ )}px`
640
+ },
641
+ children: [
642
+ /* @__PURE__ */ jsx("div", { className: "desktop-dock__hover-panel-title", children: item.label }),
643
+ agentReason ? /* @__PURE__ */ jsx("div", { className: "desktop-dock__hover-panel-description", children: agentReason }) : null,
644
+ agentActions.length ? /* @__PURE__ */ jsx("div", { className: "desktop-dock__hover-actions", children: agentActions.map((action) => {
645
+ const isPending = isAgentActionPending?.(item, action.id) ?? false;
646
+ return /* @__PURE__ */ jsx(
647
+ "button",
648
+ {
649
+ "aria-busy": isPending || void 0,
650
+ className: "desktop-dock__hover-action",
651
+ disabled: isPending,
652
+ type: "button",
653
+ onClick: (event) => {
654
+ event.preventDefault();
655
+ event.stopPropagation();
656
+ if (isPending) {
657
+ return;
658
+ }
659
+ onRunAgentAction?.(item, action.id);
660
+ },
661
+ children: resolveAgentActionLabel(
662
+ isPending && action.id === "install" ? "installing" : action.id,
663
+ copy,
664
+ getAgentActionLabel
665
+ )
666
+ },
667
+ action.id
668
+ );
669
+ }) }) : null
670
+ ]
671
+ }
672
+ ) : null
673
+ ]
674
+ }
675
+ );
676
+ return showComingSoonTooltip ? /* @__PURE__ */ jsx(
677
+ "span",
678
+ {
679
+ className: "workspace-launchpad-tooltip-host",
680
+ title: agentReason ?? void 0,
681
+ children: itemNode
682
+ }
683
+ ) : itemNode;
684
+ }
685
+ function useLaunchpadGridMetrics(open) {
686
+ const ref = useRef(null);
687
+ const [grid, setGrid] = useState(defaultLaunchpadGrid);
688
+ useEffect(() => {
689
+ if (!open) {
690
+ return void 0;
691
+ }
692
+ const element = ref.current;
693
+ if (!element) {
694
+ return void 0;
695
+ }
696
+ const update = () => {
697
+ const rect = element.getBoundingClientRect();
698
+ setGrid(
699
+ resolveWorkbenchLaunchpadGrid({
700
+ height: rect.height,
701
+ width: rect.width
702
+ })
703
+ );
704
+ };
705
+ update();
706
+ const resizeObserver = new ResizeObserver(update);
707
+ resizeObserver.observe(element);
708
+ return () => {
709
+ resizeObserver.disconnect();
710
+ };
711
+ }, [open]);
712
+ return { grid, ref };
713
+ }
714
+ function resolveLaunchpadAgentActions(item) {
715
+ if (item.comingSoon) {
716
+ return [];
717
+ }
718
+ if (item.actions?.length) {
719
+ return item.actions;
720
+ }
721
+ if (item.action) {
722
+ return [{ id: item.action }];
723
+ }
724
+ return [];
725
+ }
726
+ function resolveAgentActionLabel(actionId, copy, getAgentActionLabel) {
727
+ if (getAgentActionLabel) {
728
+ return getAgentActionLabel(actionId);
729
+ }
730
+ switch (actionId) {
731
+ case "install":
732
+ return copy.installAction ?? actionId;
733
+ case "installing":
734
+ return copy.installingAction ?? copy.installAction ?? actionId;
735
+ case "sync":
736
+ return copy.syncAction ?? actionId;
737
+ default:
738
+ return copy.refreshAction ?? actionId;
739
+ }
740
+ }
741
+ function isTextInputTarget(target) {
742
+ return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLElement && target.isContentEditable;
743
+ }
744
+ function isLaunchpadInteractiveTarget(target) {
745
+ return target instanceof HTMLElement && Boolean(
746
+ target.closest(
747
+ ".workspace-launchpad-overlay__topbar, .workspace-launchpad-search, .workspace-launchpad-item, .desktop-dock__hover-panel, .workspace-launchpad-pages"
748
+ )
749
+ );
750
+ }
751
+ function isLaunchpadWheelIgnoredTarget(target) {
752
+ return isTextInputTarget(target) || target instanceof HTMLElement && Boolean(
753
+ target.closest(
754
+ ".workspace-launchpad-overlay__topbar, .workspace-launchpad-search, .desktop-dock__hover-panel, .workspace-launchpad-pages"
755
+ )
756
+ );
757
+ }
758
+ function classNames(...values) {
759
+ return values.filter(Boolean).join(" ");
760
+ }
761
+
762
+ // src/workbenchLaunchpadDockEntry.ts
763
+ import { createElement } from "react";
764
+ function createWorkbenchLaunchpadDockIcon(input) {
765
+ return createElement(
766
+ "span",
767
+ {
768
+ "aria-hidden": "true",
769
+ className: "workspace-launchpad-dock-icon"
770
+ },
771
+ input.tileIconUrls.map(
772
+ (src, index) => createElement(
773
+ "span",
774
+ {
775
+ className: "workspace-launchpad-dock-icon__tile",
776
+ key: `${src}:${index}`
777
+ },
778
+ createElement("img", {
779
+ alt: "",
780
+ draggable: false,
781
+ src
782
+ })
783
+ )
784
+ )
785
+ );
786
+ }
787
+ function createWorkbenchLaunchpadDockEntry(input) {
788
+ return {
789
+ clickActionId: workbenchLaunchpadDockActionId,
790
+ icon: createWorkbenchLaunchpadDockIcon({
791
+ tileIconUrls: input.tileIconUrls
792
+ }),
793
+ id: workbenchLaunchpadDockEntryId,
794
+ label: input.label,
795
+ launchBehavior: "enabled",
796
+ order: input.order ?? 0,
797
+ sectionId: "launchpad",
798
+ typeId: workbenchLaunchpadDockEntryId,
799
+ visibility: "always"
800
+ };
801
+ }
802
+ export {
803
+ WorkbenchLaunchpadOverlay,
804
+ buildWorkbenchLaunchpadItems,
805
+ createWorkbenchLaunchpadDockEntry,
806
+ createWorkbenchLaunchpadDockIcon,
807
+ createWorkbenchLaunchpadWheelNavigationState,
808
+ filterWorkbenchLaunchpadItems,
809
+ paginateWorkbenchLaunchpadItems,
810
+ resolveWorkbenchLaunchpadGrid,
811
+ resolveWorkbenchLaunchpadPreviewIconUrls,
812
+ resolveWorkbenchLaunchpadWheelNavigation,
813
+ workbenchLaunchpadDockActionId,
814
+ workbenchLaunchpadDockEntryId
815
+ };
816
+ //# sourceMappingURL=index.js.map