@floegence/floe-webapp-core 0.42.4 → 0.43.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.
@@ -0,0 +1,20 @@
1
+ import { type ComponentContext, type FloeComponent } from '../context/ComponentRegistry';
2
+ export interface FloeRegistryContributionsProps<TProtocol = unknown> {
3
+ /** Reactive contribution inventory. Membership is reconciled by stable component id. */
4
+ components?: readonly FloeComponent<TProtocol>[];
5
+ /** Optional protocol resolver to inject into ComponentContext.protocol. */
6
+ getProtocol?: () => TProtocol;
7
+ /** Optional hook to customize the per-component context. */
8
+ createComponentContext?: (id: string, base: ComponentContext<TProtocol>) => ComponentContext<TProtocol>;
9
+ /** Receives validation or lifecycle failures without poisoning later reconciliation. */
10
+ onError?: (error: unknown) => void;
11
+ }
12
+ /**
13
+ * Dynamically projects host-neutral Floe components into the shared registry.
14
+ *
15
+ * Reconciliation is serialized so an id is fully unmounted and unregistered
16
+ * before a later operation for that id begins. Updating a retained id refreshes
17
+ * its registry projection without restarting its lifecycle, preserving existing
18
+ * KeepAlive views and component-local state.
19
+ */
20
+ export declare function FloeRegistryContributions<TProtocol = unknown>(props: FloeRegistryContributionsProps<TProtocol>): null;
@@ -0,0 +1,97 @@
1
+ import { createEffect as y, onCleanup as C } from "solid-js";
2
+ import { useComponentRegistry as w, useComponentContextFactory as b } from "../context/ComponentRegistry.js";
3
+ function h(r) {
4
+ const t = {};
5
+ return t.current = r, t.wrapper = {
6
+ get id() {
7
+ return t.current.id;
8
+ },
9
+ get name() {
10
+ return t.current.name;
11
+ },
12
+ get icon() {
13
+ return t.current.icon;
14
+ },
15
+ get description() {
16
+ return t.current.description;
17
+ },
18
+ get component() {
19
+ return t.current.component;
20
+ },
21
+ get sidebar() {
22
+ return t.current.sidebar;
23
+ },
24
+ get commands() {
25
+ return t.current.commands;
26
+ },
27
+ get statusBar() {
28
+ return t.current.statusBar;
29
+ },
30
+ async onMount(o) {
31
+ const e = t.current;
32
+ t.mountedDefinition = e, await e.onMount?.(o);
33
+ },
34
+ async onUnmount() {
35
+ const o = t.mountedDefinition;
36
+ try {
37
+ await o?.onUnmount?.();
38
+ } finally {
39
+ t.mountedDefinition = void 0;
40
+ }
41
+ }
42
+ }, t;
43
+ }
44
+ function F(r) {
45
+ const t = /* @__PURE__ */ new Map();
46
+ for (const o of r) {
47
+ const e = o.id.trim();
48
+ if (!e) throw new Error("FloeRegistryContributions requires a non-empty component id.");
49
+ if (e !== o.id)
50
+ throw new Error(`FloeRegistryContributions component id must be canonical: ${o.id}`);
51
+ if (t.has(e))
52
+ throw new Error(`FloeRegistryContributions received duplicate component id: ${e}`);
53
+ t.set(e, o);
54
+ }
55
+ return t;
56
+ }
57
+ function E(r) {
58
+ const t = w(), o = b(), e = /* @__PURE__ */ new Map();
59
+ let a = !1, d = Promise.resolve(), m = (i) => console.error("[FloeRegistryContributions]", i);
60
+ const l = (i) => {
61
+ d = d.then(i).catch((u) => {
62
+ m(u);
63
+ });
64
+ };
65
+ return y(() => {
66
+ const i = [...r.components ?? []], u = r.getProtocol, f = r.createComponentContext;
67
+ m = r.onError ?? ((s) => console.error("[FloeRegistryContributions]", s)), l(async () => {
68
+ if (a) return;
69
+ const s = F(i);
70
+ for (const n of [...e.keys()])
71
+ s.has(n) || (await t.unregister(n), e.delete(n));
72
+ for (const [n, p] of s) {
73
+ let c = e.get(n);
74
+ if (c)
75
+ c.current = p;
76
+ else {
77
+ if (t.getComponent(n))
78
+ throw new Error(`FloeRegistryContributions cannot replace an existing registry component: ${n}`);
79
+ c = h(p), e.set(n, c);
80
+ }
81
+ if (t.register(c.wrapper), t.mountedComponents().has(n)) continue;
82
+ const g = o(n, {
83
+ protocol: u?.()
84
+ });
85
+ await t.mount(n, f ? f(n, g) : g);
86
+ }
87
+ });
88
+ }), C(() => {
89
+ a = !0, l(async () => {
90
+ for (const i of [...e.keys()])
91
+ await t.unregister(i), e.delete(i);
92
+ });
93
+ }), null;
94
+ }
95
+ export {
96
+ E as FloeRegistryContributions
97
+ };
@@ -2,3 +2,4 @@ export { FloeProvider, type FloeProviderProps } from './FloeProvider';
2
2
  export { FloeApp, type FloeAppProps } from './FloeApp';
3
3
  export { ActivityAppsMain, type ActivityAppsMainProps } from './ActivityAppsMain';
4
4
  export { FloeRegistryRuntime, type FloeRegistryRuntimeProps } from './FloeRegistryRuntime';
5
+ export { FloeRegistryContributions, type FloeRegistryContributionsProps, } from './FloeRegistryContributions';
package/dist/app.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import { ActivityAppsMain as e } from "./app/ActivityAppsMain.js";
2
- import { FloeApp as t } from "./app/FloeApp.js";
2
+ import { FloeApp as i } from "./app/FloeApp.js";
3
3
  import { FloeProvider as m } from "./app/FloeProvider.js";
4
- import { FloeRegistryRuntime as x } from "./app/FloeRegistryRuntime.js";
4
+ import { FloeRegistryContributions as x } from "./app/FloeRegistryContributions.js";
5
+ import { FloeRegistryRuntime as n } from "./app/FloeRegistryRuntime.js";
5
6
  export {
6
7
  e as ActivityAppsMain,
7
- t as FloeApp,
8
+ i as FloeApp,
8
9
  m as FloeProvider,
9
- x as FloeRegistryRuntime
10
+ x as FloeRegistryContributions,
11
+ n as FloeRegistryRuntime
10
12
  };
@@ -1,5 +1,6 @@
1
- import { type Component } from 'solid-js';
1
+ import { type Component, type JSX } from 'solid-js';
2
2
  import { type ActivityBarCollapseBehavior } from './activityBarBehavior';
3
+ import { type BarItemContextMenuHandler } from './barItemContextMenu';
3
4
  export interface ActivityBarItem {
4
5
  id: string;
5
6
  icon: Component<{
@@ -8,6 +9,8 @@ export interface ActivityBarItem {
8
9
  label: string;
9
10
  badge?: number | string | (() => number | string | undefined);
10
11
  onClick?: () => void;
12
+ /** Requests a product-owned context menu for this concrete Activity Bar button. */
13
+ onContextMenu?: BarItemContextMenuHandler;
11
14
  /** Receives the concrete trigger button for anchored product surfaces. */
12
15
  buttonRef?: (button: HTMLButtonElement | null) => void;
13
16
  /** ARIA disclosure state for a surface controlled by this item. */
@@ -40,4 +43,4 @@ export interface ActivityBarProps {
40
43
  * VSCode-style Activity Bar
41
44
  * Vertical icon strip on the left side
42
45
  */
43
- export declare function ActivityBar(props: ActivityBarProps): import("solid-js").JSX.Element;
46
+ export declare function ActivityBar(props: ActivityBarProps): JSX.Element;
@@ -1,104 +1,116 @@
1
- import { insert as c, createComponent as r, effect as g, className as h, setAttribute as u, template as s, use as I, Dynamic as B, memo as $, delegateEvents as E } from "solid-js/web";
2
- import { createMemo as m, For as k, Show as x, onMount as S, onCleanup as _ } from "solid-js";
1
+ import { insert as l, createComponent as o, effect as g, className as x, setAttribute as u, template as v, use as $, Dynamic as B, memo as D, delegateEvents as E } from "solid-js/web";
2
+ import { createMemo as s, For as I, Show as h, onMount as R, onCleanup as S } from "solid-js";
3
3
  import { cn as y } from "../../utils/cn.js";
4
- import { deferNonBlocking as j } from "../../utils/defer.js";
5
- import { Tooltip as R } from "../ui/Tooltip.js";
6
- import { resolveActivityBarClick as M } from "./activityBarBehavior.js";
7
- var N = /* @__PURE__ */ s('<div class="flex flex-col">'), q = /* @__PURE__ */ s('<nav data-floe-shell-slot=activity-bar style=border-right-color:var(--activity-bar-border)><div class="flex flex-col">'), D = /* @__PURE__ */ s('<div class="absolute left-0 top-0 w-1 h-full bg-primary rounded-r">'), F = /* @__PURE__ */ s("<span>"), H = /* @__PURE__ */ s("<button type=button>");
8
- function J(t) {
9
- const l = m(() => t.activeId), d = m(() => t.collapsed ?? !1), b = m(() => t.onActiveChange), i = m(() => t.onCollapsedChange), v = (e) => {
4
+ import { deferNonBlocking as _ } from "../../utils/defer.js";
5
+ import { Tooltip as j } from "../ui/Tooltip.js";
6
+ import { resolveActivityBarClick as P } from "./activityBarBehavior.js";
7
+ import { isBarItemContextMenuKey as T, keyboardBarItemContextMenuRequest as q } from "./barItemContextMenu.js";
8
+ var K = /* @__PURE__ */ v('<div class="flex flex-col">'), N = /* @__PURE__ */ v('<nav data-floe-shell-slot=activity-bar style=border-right-color:var(--activity-bar-border)><div class="flex flex-col">'), X = /* @__PURE__ */ v('<div class="absolute left-0 top-0 w-1 h-full bg-primary rounded-r">'), Y = /* @__PURE__ */ v("<span>"), F = /* @__PURE__ */ v("<button type=button>");
9
+ function Q(t) {
10
+ const a = s(() => t.activeId), c = s(() => t.collapsed ?? !1), f = s(() => t.onActiveChange), b = s(() => t.onCollapsedChange), m = (e) => {
10
11
  if (e.onClick) {
11
- j(() => e.onClick());
12
+ _(() => e.onClick());
12
13
  return;
13
14
  }
14
- const a = M({
15
+ const i = P({
15
16
  clickedId: e.id,
16
- activeId: l(),
17
- collapsed: d(),
17
+ activeId: a(),
18
+ collapsed: c(),
18
19
  behavior: e.collapseBehavior
19
20
  });
20
- a.nextActiveId !== l() && (typeof a.openSidebar == "boolean" ? b()(a.nextActiveId, {
21
- openSidebar: a.openSidebar
22
- }) : b()(a.nextActiveId)), typeof a.nextCollapsed == "boolean" && i()?.(a.nextCollapsed);
21
+ i.nextActiveId !== a() && (typeof i.openSidebar == "boolean" ? f()(i.nextActiveId, {
22
+ openSidebar: i.openSidebar
23
+ }) : f()(i.nextActiveId)), typeof i.nextCollapsed == "boolean" && b()?.(i.nextCollapsed);
23
24
  };
24
25
  return (() => {
25
- var e = q(), a = e.firstChild;
26
- return c(a, r(k, {
26
+ var e = N(), i = e.firstChild;
27
+ return l(i, o(I, {
27
28
  get each() {
28
29
  return t.items;
29
30
  },
30
- children: (n) => r(w, {
31
+ children: (n) => o(M, {
31
32
  item: n,
32
33
  get isActive() {
33
- return l() === n.id;
34
+ return a() === n.id;
34
35
  },
35
- onClick: () => v(n)
36
+ onClick: () => m(n)
36
37
  })
37
- })), c(e, r(x, {
38
+ })), l(e, o(h, {
38
39
  get when() {
39
40
  return t.bottomItems?.length;
40
41
  },
41
42
  get children() {
42
- var n = N();
43
- return c(n, r(k, {
43
+ var n = K();
44
+ return l(n, o(I, {
44
45
  get each() {
45
46
  return t.bottomItems;
46
47
  },
47
- children: (o) => r(w, {
48
- item: o,
48
+ children: (r) => o(M, {
49
+ item: r,
49
50
  isActive: !1,
50
- onClick: () => v(o)
51
+ onClick: () => m(r)
51
52
  })
52
53
  })), n;
53
54
  }
54
55
  }), null), g((n) => {
55
- var o = y("w-10 md:w-12 flex flex-col justify-between shrink-0 min-h-0", "bg-activity-bar border-r border-border", t.class), f = t.ariaLabel;
56
- return o !== n.e && h(e, n.e = o), f !== n.t && u(e, "aria-label", n.t = f), n;
56
+ var r = y("w-10 md:w-12 flex flex-col justify-between shrink-0 min-h-0", "bg-activity-bar border-r border-border", t.class), d = t.ariaLabel;
57
+ return r !== n.e && x(e, n.e = r), d !== n.t && u(e, "aria-label", n.t = d), n;
57
58
  }, {
58
59
  e: void 0,
59
60
  t: void 0
60
61
  }), e;
61
62
  })();
62
63
  }
63
- function w(t) {
64
- let l;
65
- const d = () => typeof t.item.badge == "function" ? t.item.badge() : t.item.badge, b = () => typeof t.item.ariaExpanded == "function" ? t.item.ariaExpanded() : t.item.ariaExpanded;
66
- return S(() => t.item.buttonRef?.(l ?? null)), _(() => t.item.buttonRef?.(null)), r(R, {
64
+ function M(t) {
65
+ let a;
66
+ const c = () => typeof t.item.badge == "function" ? t.item.badge() : t.item.badge, f = () => typeof t.item.ariaExpanded == "function" ? t.item.ariaExpanded() : t.item.ariaExpanded;
67
+ R(() => t.item.buttonRef?.(a ?? null)), S(() => t.item.buttonRef?.(null));
68
+ const b = (e) => {
69
+ t.item.onContextMenu && (e.preventDefault(), e.stopPropagation(), t.item.onContextMenu({
70
+ trigger: e.currentTarget,
71
+ clientX: e.clientX,
72
+ clientY: e.clientY,
73
+ source: "pointer"
74
+ }));
75
+ }, m = (e) => {
76
+ !t.item.onContextMenu || !T(e) || (e.preventDefault(), e.stopPropagation(), t.item.onContextMenu(q(e.currentTarget)));
77
+ };
78
+ return o(j, {
67
79
  get content() {
68
80
  return t.item.label;
69
81
  },
70
82
  placement: "right",
71
83
  delay: 0,
72
84
  get children() {
73
- var i = H();
74
- i.$$click = () => t.onClick();
75
- var v = l;
76
- return typeof v == "function" ? I(v, i) : l = i, c(i, r(x, {
85
+ var e = F();
86
+ e.$$keydown = m, e.$$contextmenu = b, e.$$click = () => t.onClick();
87
+ var i = a;
88
+ return typeof i == "function" ? $(i, e) : a = e, l(e, o(h, {
77
89
  get when() {
78
90
  return t.isActive;
79
91
  },
80
92
  get children() {
81
- return D();
93
+ return X();
82
94
  }
83
- }), null), c(i, r(B, {
95
+ }), null), l(e, o(B, {
84
96
  get component() {
85
97
  return t.item.icon;
86
98
  },
87
99
  class: "w-5 h-5"
88
- }), null), c(i, r(x, {
100
+ }), null), l(e, o(h, {
89
101
  get when() {
90
- return d();
102
+ return c();
91
103
  },
92
104
  get children() {
93
- var e = F();
94
- return c(e, (() => {
95
- var a = $(() => typeof d() == "number" && d() > 99);
96
- return () => a() ? "99+" : d();
97
- })()), g(() => h(e, y("absolute top-0.5 right-0.5 min-w-3.5 h-3.5 px-1", "flex items-center justify-center", "text-[9px] font-medium rounded-full", "bg-activity-bar-badge text-activity-bar-badge-foreground"))), e;
105
+ var n = Y();
106
+ return l(n, (() => {
107
+ var r = D(() => typeof c() == "number" && c() > 99);
108
+ return () => r() ? "99+" : c();
109
+ })()), g(() => x(n, y("absolute top-0.5 right-0.5 min-w-3.5 h-3.5 px-1", "flex items-center justify-center", "text-[9px] font-medium rounded-full", "bg-activity-bar-badge text-activity-bar-badge-foreground"))), n;
98
110
  }
99
- }), null), g((e) => {
100
- var a = y("relative w-full aspect-square flex items-center justify-center cursor-pointer", "focus:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-inset", t.isActive ? "text-activity-bar-foreground-active bg-accent" : "text-activity-bar-foreground transition-[color,background-color] duration-100 hover:text-activity-bar-foreground-active hover:bg-accent/50"), n = t.item.label, o = t.isActive, f = b(), C = t.item.ariaControls, A = t.item.ariaHasPopup;
101
- return a !== e.e && h(i, e.e = a), n !== e.t && u(i, "aria-label", e.t = n), o !== e.a && u(i, "aria-pressed", e.a = o), f !== e.o && u(i, "aria-expanded", e.o = f), C !== e.i && u(i, "aria-controls", e.i = C), A !== e.n && u(i, "aria-haspopup", e.n = A), e;
111
+ }), null), g((n) => {
112
+ var r = y("relative w-full aspect-square flex items-center justify-center cursor-pointer", "focus:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-inset", t.isActive ? "text-activity-bar-foreground-active bg-accent" : "text-activity-bar-foreground transition-[color,background-color] duration-100 hover:text-activity-bar-foreground-active hover:bg-accent/50"), d = t.item.label, C = t.isActive, k = f(), w = t.item.ariaControls, A = t.item.ariaHasPopup ?? (t.item.onContextMenu ? "menu" : void 0);
113
+ return r !== n.e && x(e, n.e = r), d !== n.t && u(e, "aria-label", n.t = d), C !== n.a && u(e, "aria-pressed", n.a = C), k !== n.o && u(e, "aria-expanded", n.o = k), w !== n.i && u(e, "aria-controls", n.i = w), A !== n.n && u(e, "aria-haspopup", n.n = A), n;
102
114
  }, {
103
115
  e: void 0,
104
116
  t: void 0,
@@ -106,11 +118,11 @@ function w(t) {
106
118
  o: void 0,
107
119
  i: void 0,
108
120
  n: void 0
109
- }), i;
121
+ }), e;
110
122
  }
111
123
  });
112
124
  }
113
- E(["click"]);
125
+ E(["click", "contextmenu", "keydown"]);
114
126
  export {
115
- J as ActivityBar
127
+ Q as ActivityBar
116
128
  };
@@ -0,0 +1,12 @@
1
+ export type BarItemContextMenuSource = 'pointer' | 'keyboard';
2
+ export type BarItemContextMenuRequest = Readonly<{
3
+ /** Concrete bar button that owns the requested menu. */
4
+ trigger: HTMLButtonElement;
5
+ /** Viewport-space menu anchor. Pointer requests preserve the exact pointer position. */
6
+ clientX: number;
7
+ clientY: number;
8
+ source: BarItemContextMenuSource;
9
+ }>;
10
+ export type BarItemContextMenuHandler = (request: BarItemContextMenuRequest) => void;
11
+ export declare function isBarItemContextMenuKey(event: KeyboardEvent): boolean;
12
+ export declare function keyboardBarItemContextMenuRequest(trigger: HTMLButtonElement): BarItemContextMenuRequest;
@@ -0,0 +1,16 @@
1
+ function n(e) {
2
+ return e.key === "ContextMenu" || e.shiftKey && e.key === "F10";
3
+ }
4
+ function o(e) {
5
+ const t = e.getBoundingClientRect();
6
+ return {
7
+ trigger: e,
8
+ clientX: t.left + t.width / 2,
9
+ clientY: t.top + t.height / 2,
10
+ source: "keyboard"
11
+ };
12
+ }
13
+ export {
14
+ n as isBarItemContextMenuKey,
15
+ o as keyboardBarItemContextMenuRequest
16
+ };
@@ -1,14 +1,15 @@
1
1
  export { Shell, type ShellProps, type ShellSlotClassNames } from './Shell';
2
2
  export { ActivityBar, type ActivityBarItem, type ActivityBarProps } from './ActivityBar';
3
+ export type { BarItemContextMenuHandler, BarItemContextMenuRequest, BarItemContextMenuSource, } from './barItemContextMenu';
3
4
  export { Sidebar, SidebarContent, SidebarItemList, SidebarSection, SidebarItem, type SidebarProps, type SidebarContentProps, type SidebarItemListProps, type SidebarSectionProps, type SidebarItemProps, } from './Sidebar';
4
5
  export { TopBar, type TopBarProps } from './TopBar';
5
6
  export { TopBarIconButton, type TopBarIconButtonProps } from './TopBarIconButton';
6
- export { BottomBar, BottomBarItem, StatusIndicator, type BottomBarProps, type BottomBarItemProps, type StatusIndicatorProps } from './BottomBar';
7
+ export { BottomBar, BottomBarItem, StatusIndicator, type BottomBarProps, type BottomBarItemProps, type StatusIndicatorProps, } from './BottomBar';
7
8
  export { BottomBarCompanion, type BottomBarCompanionDismissReason, type BottomBarCompanionPhase, type BottomBarCompanionProps, } from './BottomBarCompanion';
8
9
  export { MobileTabBar, type MobileTabBarItem, type MobileTabBarProps } from './MobileTabBar';
9
10
  export { ResizeHandle, type ResizeHandleProps } from './ResizeHandle';
10
11
  export { SidebarPane, type SidebarPaneProps } from './SidebarPane';
11
- export { Panel, PanelHeader, PanelContent, type PanelProps, type PanelHeaderProps, type PanelContentProps } from './Panel';
12
+ export { Panel, PanelHeader, PanelContent, type PanelProps, type PanelHeaderProps, type PanelContentProps, } from './Panel';
12
13
  export { KeepAliveStack, type KeepAliveStackProps, type KeepAliveView } from './KeepAliveStack';
13
14
  export { DisplayModeSwitcher, sanitizeDisplayMode, type DisplayMode, type DisplayModeSwitcherProps, } from './DisplayModeSwitcher';
14
15
  export { DisplayModePageShell, type DisplayModePageShellProps } from './DisplayModePageShell';
@@ -1,4 +1,5 @@
1
1
  import { type Component, type JSX } from 'solid-js';
2
+ import { type BarItemContextMenuHandler } from '../layout/barItemContextMenu';
2
3
  import { type WorkbenchWidgetFrame } from './workbenchHelpers';
3
4
  import { type WorkbenchEdgeAutoPanFrame } from './workbenchEdgeAutoPan';
4
5
  import type { WorkbenchDockToolId, WorkbenchInteractionMode, WorkbenchViewport, WorkbenchWidgetDefinition, WorkbenchWidgetItem, WorkbenchWidgetType } from './types';
@@ -46,6 +47,8 @@ export type WorkbenchHostDockItem = Readonly<{
46
47
  }>;
47
48
  active?: boolean;
48
49
  onActivate?: (trigger: HTMLButtonElement) => void;
50
+ /** Requests a product-owned context menu for this concrete Dock button. */
51
+ onContextMenu?: BarItemContextMenuHandler;
49
52
  canvasPlacement?: WorkbenchDockCanvasPlacement;
50
53
  }>;
51
54
  export type WorkbenchDockItemActivation = Readonly<{