bubble-toolbar 0.1.0 → 1.1.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/dist/index.js CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,37 +15,69 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/index.ts
31
21
  var index_exports = {};
32
22
  __export(index_exports, {
33
23
  BubbleArea: () => BubbleArea,
24
+ BubbleBreadcrumbSearch: () => BubbleBreadcrumbSearch,
34
25
  BubbleGroup: () => BubbleGroup,
35
26
  BubbleIconButton: () => BubbleIconButton,
27
+ BubbleSearch: () => BubbleSearch,
36
28
  BubbleToolbar: () => BubbleToolbar,
37
29
  BubbleToolbarContext: () => BubbleToolbarContext,
38
30
  formatPx: () => formatPx,
39
31
  getGlassStyles: () => getGlassStyles,
40
32
  getValidChildren: () => getValidChildren,
41
33
  resolveSize: () => resolveSize,
42
- useBubbleToolbar: () => useBubbleToolbar
34
+ useBubbleActivity: () => useBubbleActivity,
35
+ useBubbleLiveActivity: () => useBubbleLiveActivity,
36
+ useBubbleToolbar: () => useBubbleToolbar,
37
+ useBubbleToolbarLiveActivity: () => useBubbleToolbarLiveActivity
43
38
  });
44
39
  module.exports = __toCommonJS(index_exports);
45
40
 
46
- // src/BubbleToolbar.tsx
41
+ // src/activity.ts
42
+ var import_react = require("react");
43
+ var BubbleActivityContext = (0, import_react.createContext)(null);
44
+ var useBubbleActivity = () => {
45
+ const activity = (0, import_react.useContext)(BubbleActivityContext);
46
+ if (!activity) {
47
+ throw new Error("useBubbleActivity must be used inside a BubbleGroup or BubbleToolbar.Group.");
48
+ }
49
+ return activity;
50
+ };
51
+ var useGroupActivity = (externalBusy) => {
52
+ const [pending, setPending] = (0, import_react.useState)(0);
53
+ const mounted = (0, import_react.useRef)(true);
54
+ (0, import_react.useEffect)(() => {
55
+ mounted.current = true;
56
+ return () => {
57
+ mounted.current = false;
58
+ };
59
+ }, []);
60
+ const run = (0, import_react.useCallback)(async (task) => {
61
+ if (mounted.current) {
62
+ setPending((count) => count + 1);
63
+ }
64
+ try {
65
+ return await task();
66
+ } finally {
67
+ if (mounted.current) {
68
+ setPending((count) => count - 1);
69
+ }
70
+ }
71
+ }, []);
72
+ const isBusy = externalBusy || pending > 0;
73
+ return (0, import_react.useMemo)(() => ({ isBusy, run }), [isBusy, run]);
74
+ };
75
+
76
+ // src/BubbleGroup.tsx
47
77
  var import_react4 = require("react");
48
78
 
49
79
  // src/context.ts
50
- var import_react = require("react");
80
+ var import_react2 = require("react");
51
81
  var defaultContextValue = {
52
82
  size: "md",
53
83
  theme: "glass",
@@ -57,26 +87,21 @@ var defaultContextValue = {
57
87
  bordered: true,
58
88
  shadow: true
59
89
  };
60
- var BubbleToolbarContext = (0, import_react.createContext)(defaultContextValue);
90
+ var BubbleToolbarContext = (0, import_react2.createContext)(defaultContextValue);
61
91
  var useBubbleToolbar = () => {
62
- return (0, import_react.useContext)(BubbleToolbarContext);
92
+ return (0, import_react2.useContext)(BubbleToolbarContext);
63
93
  };
64
94
 
65
- // src/BubbleGroup.tsx
66
- var import_react3 = require("react");
67
-
68
95
  // src/utils/children.ts
69
- var import_react2 = __toESM(require("react"));
96
+ var import_react3 = require("react");
70
97
  function getValidChildren(children) {
71
98
  const result = [];
72
- import_react2.default.Children.forEach(children, (child) => {
99
+ import_react3.Children.forEach(children, (child) => {
73
100
  if (child === null || child === void 0 || typeof child === "boolean") {
74
101
  return;
75
102
  }
76
- if (import_react2.default.isValidElement(child) && child.type === import_react2.default.Fragment) {
77
- const fragmentChildren = getValidChildren(
78
- child.props.children
79
- );
103
+ if ((0, import_react3.isValidElement)(child) && child.type === import_react3.Fragment) {
104
+ const fragmentChildren = getValidChildren(child.props.children);
80
105
  result.push(...fragmentChildren);
81
106
  } else {
82
107
  result.push(child);
@@ -87,13 +112,14 @@ function getValidChildren(children) {
87
112
 
88
113
  // src/utils/styles.ts
89
114
  function resolveSize(size) {
90
- if (typeof size === "number") return size;
115
+ if (typeof size === "number") {
116
+ return size;
117
+ }
91
118
  switch (size) {
92
119
  case "sm":
93
120
  return 40;
94
121
  case "lg":
95
122
  return 56;
96
- case "md":
97
123
  default:
98
124
  return 48;
99
125
  }
@@ -108,28 +134,27 @@ function getGlassStyles(theme, blurred, blurAmount, bordered, shadow) {
108
134
  let color;
109
135
  switch (theme) {
110
136
  case "dark":
111
- background = blurred ? "rgba(24, 24, 27, 0.72)" : "rgba(24, 24, 27, 0.95)";
112
- borderColor = "rgba(255, 255, 255, 0.12)";
113
- color = "#f4f4f5";
137
+ background = blurred ? "var(--bubble-dark-bg, rgba(24, 24, 27, 0.72))" : "var(--bubble-dark-bg-solid, rgba(24, 24, 27, 0.95))";
138
+ borderColor = "var(--bubble-dark-border, rgba(255, 255, 255, 0.12))";
139
+ color = "var(--bubble-dark-fg, #f4f4f5)";
114
140
  break;
115
141
  case "light":
116
- background = blurred ? "rgba(255, 255, 255, 0.75)" : "rgba(255, 255, 255, 0.95)";
117
- borderColor = "rgba(0, 0, 0, 0.08)";
118
- color = "#18181b";
142
+ background = blurred ? "var(--bubble-light-bg, rgba(255, 255, 255, 0.75))" : "var(--bubble-light-bg-solid, rgba(255, 255, 255, 0.95))";
143
+ borderColor = "var(--bubble-light-border, rgba(0, 0, 0, 0.08))";
144
+ color = "var(--bubble-light-fg, #18181b)";
119
145
  break;
120
146
  case "auto":
121
- background = "var(--bubble-bg, rgba(255, 255, 255, 0.75))";
122
- borderColor = "var(--bubble-border, rgba(0, 0, 0, 0.08))";
123
- color = "var(--bubble-fg, #18181b)";
147
+ background = "var(--bubble-bg, var(--bubble-light-bg, rgba(255, 255, 255, 0.75)))";
148
+ borderColor = "var(--bubble-border, var(--bubble-light-border, rgba(0, 0, 0, 0.08)))";
149
+ color = "var(--bubble-fg, var(--bubble-light-fg, #18181b))";
124
150
  break;
125
- case "glass":
126
151
  default:
127
- background = blurred ? "rgba(255, 255, 255, 0.65)" : "rgba(255, 255, 255, 0.92)";
128
- borderColor = "rgba(255, 255, 255, 0.4)";
129
- color = "#09090b";
152
+ background = blurred ? "var(--bubble-glass-bg, rgba(255, 255, 255, 0.65))" : "var(--bubble-glass-bg-solid, rgba(255, 255, 255, 0.92))";
153
+ borderColor = "var(--bubble-glass-border, rgba(255, 255, 255, 0.4))";
154
+ color = "var(--bubble-glass-fg, #09090b)";
130
155
  break;
131
156
  }
132
- const boxShadow = typeof shadow === "string" ? shadow : shadow ? "0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.06)" : "none";
157
+ const boxShadow = typeof shadow === "string" ? shadow : shadow ? "var(--bubble-glass-shadow, 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.06))" : "none";
133
158
  return {
134
159
  backgroundColor: background,
135
160
  backdropFilter: blurred ? `blur(${blurVal}) saturate(180%)` : void 0,
@@ -143,17 +168,23 @@ function getGlassStyles(theme, blurred, blurAmount, bordered, shadow) {
143
168
 
144
169
  // src/BubbleGroup.tsx
145
170
  var import_jsx_runtime = require("react/jsx-runtime");
146
- var BubbleGroup = (0, import_react3.forwardRef)(
171
+ var BubbleGroup = (0, import_react4.forwardRef)(
147
172
  ({
148
173
  children,
149
174
  shape: explicitShape,
150
175
  size: explicitSize,
151
176
  itemGap: explicitItemGap,
177
+ busy = false,
178
+ activityColors,
179
+ activityDuration,
152
180
  className = "",
153
181
  style,
154
182
  ...rest
155
183
  }, ref) => {
156
184
  const context = useBubbleToolbar();
185
+ const activity = useGroupActivity(Boolean(context.busy || busy));
186
+ const colors = activityColors ?? context.activityColors ?? ["#22d3ee", "#8b5cf6", "#ec4899"];
187
+ const duration = activityDuration ?? context.activityDuration ?? 2.4;
157
188
  const validChildren = getValidChildren(children);
158
189
  const count = validChildren.length;
159
190
  const computedShape = explicitShape || (count <= 1 ? "circle" : "pill");
@@ -168,6 +199,10 @@ var BubbleGroup = (0, import_react3.forwardRef)(
168
199
  );
169
200
  const isCircle = computedShape === "circle";
170
201
  const groupStyles = {
202
+ "--bubble-activity-color-1": colors[0],
203
+ "--bubble-activity-color-2": colors[1],
204
+ "--bubble-activity-color-3": colors[2],
205
+ "--bubble-activity-duration": `${Number.isFinite(duration) && duration > 0 ? duration : 2.4}s`,
171
206
  ...glassStyles,
172
207
  height: `${effectiveSize}px`,
173
208
  boxSizing: "border-box",
@@ -196,9 +231,10 @@ var BubbleGroup = (0, import_react3.forwardRef)(
196
231
  const combinedClassName = [
197
232
  "bubble-group",
198
233
  `bubble-group--${computedShape}`,
234
+ activity.isBusy && "bubble-group--busy",
199
235
  className
200
236
  ].filter(Boolean).join(" ");
201
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
237
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BubbleActivityContext.Provider, { value: activity, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
202
238
  "div",
203
239
  {
204
240
  ref,
@@ -208,149 +244,143 @@ var BubbleGroup = (0, import_react3.forwardRef)(
208
244
  "data-shape": computedShape,
209
245
  "data-items-count": count,
210
246
  ...rest,
247
+ "aria-busy": activity.isBusy,
211
248
  children: validChildren
212
249
  }
213
- );
250
+ ) });
214
251
  }
215
252
  );
216
253
  BubbleGroup.displayName = "BubbleGroup";
217
254
 
218
255
  // src/BubbleToolbar.tsx
219
- var import_jsx_runtime2 = require("react/jsx-runtime");
220
- var BubbleToolbar = (0, import_react4.forwardRef)(
221
- ({
222
- groups,
223
- children,
224
- position = "bottom",
225
- theme = "glass",
226
- size = "md",
227
- blurred = true,
228
- blurAmount = 16,
229
- groupGap = 8,
230
- itemGap = 8,
231
- bordered = true,
232
- shadow = true,
233
- className = "",
234
- style,
235
- ariaLabel = "Toolbar",
236
- ...rest
237
- }, ref) => {
238
- const contextValue = (0, import_react4.useMemo)(
239
- () => ({
240
- size,
241
- theme,
242
- blurred,
243
- blurAmount,
244
- itemGap,
245
- bordered,
246
- shadow
247
- }),
248
- [size, theme, blurred, blurAmount, itemGap, bordered, shadow]
249
- );
250
- const getPositionStyles = () => {
251
- switch (position) {
252
- case "bottom":
253
- return {
254
- position: "fixed",
255
- bottom: "24px",
256
- left: "50%",
257
- transform: "translateX(-50%)",
258
- zIndex: 1e3
259
- };
260
- case "top":
261
- return {
262
- position: "fixed",
263
- top: "24px",
264
- left: "50%",
265
- transform: "translateX(-50%)",
266
- zIndex: 1e3
267
- };
268
- case "static":
269
- return {
270
- position: "relative"
271
- };
272
- case "custom":
273
- default:
274
- return {};
275
- }
276
- };
277
- const containerStyles = {
278
- ...getPositionStyles(),
279
- display: "inline-flex",
280
- alignItems: "center",
281
- gap: formatPx(groupGap),
282
- maxWidth: "calc(100vw - 32px)",
283
- overflowX: "auto",
284
- overflowY: "hidden",
285
- padding: "4px",
286
- boxSizing: "border-box",
287
- scrollbarWidth: "none",
288
- // Firefox
289
- msOverflowStyle: "none",
290
- // IE/Edge
291
- WebkitOverflowScrolling: "touch",
292
- pointerEvents: "auto",
293
- ...style
294
- };
295
- const combinedClassName = [
296
- "bubble-toolbar",
297
- `bubble-toolbar--${position}`,
298
- `bubble-toolbar--${theme}`,
299
- className
300
- ].filter(Boolean).join(" ");
301
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BubbleToolbarContext.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
302
- "div",
303
- {
304
- ref,
305
- role: "toolbar",
306
- "aria-label": ariaLabel,
307
- className: combinedClassName,
308
- style: containerStyles,
309
- ...rest,
310
- children: [
311
- groups && groups.length > 0 ? groups.map((groupItems, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BubbleGroup, { children: groupItems }, index)) : null,
312
- children
313
- ]
314
- }
315
- ) });
316
- }
317
- );
318
- BubbleToolbar.displayName = "BubbleToolbar";
319
- BubbleToolbar.Group = BubbleGroup;
256
+ var import_react13 = require("react");
257
+
258
+ // src/live/BubbleLiveActivity.tsx
259
+ var import_react7 = require("react");
320
260
 
321
261
  // src/components/BubbleIconButton.tsx
322
262
  var import_react5 = require("react");
323
- var import_jsx_runtime3 = require("react/jsx-runtime");
263
+ var import_jsx_runtime2 = require("react/jsx-runtime");
324
264
  var ICONS = {
325
- search: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
326
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "11", cy: "11", r: "8" }),
327
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
328
- ] }),
329
- theme: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
330
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "5" }),
331
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "12", y1: "1", x2: "12", y2: "3" }),
332
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "12", y1: "21", x2: "12", y2: "23" }),
333
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "4.22", y1: "4.22", x2: "5.64", y2: "5.64" }),
334
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "18.36", y1: "18.36", x2: "19.78", y2: "19.78" }),
335
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "1", y1: "12", x2: "3", y2: "12" }),
336
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "21", y1: "12", x2: "23", y2: "12" }),
337
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "4.22", y1: "19.78", x2: "5.64", y2: "18.36" }),
338
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "18.36", y1: "5.64", x2: "19.78", y2: "4.22" })
339
- ] }),
340
- chat: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) }),
341
- notifications: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
342
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" }),
343
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M13.73 21a2 2 0 0 1-3.46 0" })
344
- ] }),
345
- logo: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
346
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "12 2 2 7 12 12 22 7 12 2" }),
347
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "2 17 12 22 22 17" }),
348
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "2 12 12 17 22 12" })
349
- ] }),
350
- close: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
351
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
352
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
353
- ] })
265
+ search: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
266
+ "svg",
267
+ {
268
+ "aria-hidden": "true",
269
+ focusable: "false",
270
+ width: "18",
271
+ height: "18",
272
+ viewBox: "0 0 24 24",
273
+ fill: "none",
274
+ stroke: "currentColor",
275
+ strokeWidth: "2",
276
+ strokeLinecap: "round",
277
+ strokeLinejoin: "round",
278
+ children: [
279
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "11", cy: "11", r: "8" }),
280
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
281
+ ]
282
+ }
283
+ ),
284
+ theme: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
285
+ "svg",
286
+ {
287
+ "aria-hidden": "true",
288
+ focusable: "false",
289
+ width: "18",
290
+ height: "18",
291
+ viewBox: "0 0 24 24",
292
+ fill: "none",
293
+ stroke: "currentColor",
294
+ strokeWidth: "2",
295
+ strokeLinecap: "round",
296
+ strokeLinejoin: "round",
297
+ children: [
298
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "5" }),
299
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", y1: "1", x2: "12", y2: "3" }),
300
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", y1: "21", x2: "12", y2: "23" }),
301
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "4.22", y1: "4.22", x2: "5.64", y2: "5.64" }),
302
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "18.36", y1: "18.36", x2: "19.78", y2: "19.78" }),
303
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "1", y1: "12", x2: "3", y2: "12" }),
304
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "21", y1: "12", x2: "23", y2: "12" }),
305
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "4.22", y1: "19.78", x2: "5.64", y2: "18.36" }),
306
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "18.36", y1: "5.64", x2: "19.78", y2: "4.22" })
307
+ ]
308
+ }
309
+ ),
310
+ chat: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
311
+ "svg",
312
+ {
313
+ "aria-hidden": "true",
314
+ focusable: "false",
315
+ width: "18",
316
+ height: "18",
317
+ viewBox: "0 0 24 24",
318
+ fill: "none",
319
+ stroke: "currentColor",
320
+ strokeWidth: "2",
321
+ strokeLinecap: "round",
322
+ strokeLinejoin: "round",
323
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
324
+ }
325
+ ),
326
+ notifications: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
327
+ "svg",
328
+ {
329
+ "aria-hidden": "true",
330
+ focusable: "false",
331
+ width: "18",
332
+ height: "18",
333
+ viewBox: "0 0 24 24",
334
+ fill: "none",
335
+ stroke: "currentColor",
336
+ strokeWidth: "2",
337
+ strokeLinecap: "round",
338
+ strokeLinejoin: "round",
339
+ children: [
340
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" }),
341
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M13.73 21a2 2 0 0 1-3.46 0" })
342
+ ]
343
+ }
344
+ ),
345
+ logo: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
346
+ "svg",
347
+ {
348
+ "aria-hidden": "true",
349
+ focusable: "false",
350
+ width: "20",
351
+ height: "20",
352
+ viewBox: "0 0 24 24",
353
+ fill: "none",
354
+ stroke: "currentColor",
355
+ strokeWidth: "2",
356
+ strokeLinecap: "round",
357
+ strokeLinejoin: "round",
358
+ children: [
359
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polygon", { points: "12 2 2 7 12 12 22 7 12 2" }),
360
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "2 17 12 22 22 17" }),
361
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "2 12 12 17 22 12" })
362
+ ]
363
+ }
364
+ ),
365
+ close: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
366
+ "svg",
367
+ {
368
+ "aria-hidden": "true",
369
+ focusable: "false",
370
+ width: "18",
371
+ height: "18",
372
+ viewBox: "0 0 24 24",
373
+ fill: "none",
374
+ stroke: "currentColor",
375
+ strokeWidth: "2",
376
+ strokeLinecap: "round",
377
+ strokeLinejoin: "round",
378
+ children: [
379
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
380
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
381
+ ]
382
+ }
383
+ )
354
384
  };
355
385
  var BubbleIconButton = (0, import_react5.forwardRef)(
356
386
  ({
@@ -379,12 +409,11 @@ var BubbleIconButton = (0, import_react5.forwardRef)(
379
409
  height: "36px",
380
410
  borderRadius: "50%",
381
411
  border: "none",
382
- background: active ? "rgba(0, 0, 0, 0.08)" : isHovered ? "rgba(0, 0, 0, 0.05)" : "transparent",
412
+ background: active ? "var(--bubble-button-active-bg, rgba(0, 0, 0, 0.08))" : isHovered ? "var(--bubble-button-hover-bg, rgba(0, 0, 0, 0.05))" : "transparent",
383
413
  color: "inherit",
384
414
  cursor: "pointer",
385
415
  padding: 0,
386
416
  margin: 0,
387
- outline: "none",
388
417
  transition: "background-color 0.15s ease, transform 0.15s ease",
389
418
  transform: isHovered ? "scale(1.06)" : "scale(1)",
390
419
  flexShrink: 0,
@@ -408,7 +437,7 @@ var BubbleIconButton = (0, import_react5.forwardRef)(
408
437
  justifyContent: "center",
409
438
  boxShadow: "0 0 0 1.5px rgba(255, 255, 255, 0.9)"
410
439
  };
411
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
440
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
412
441
  "button",
413
442
  {
414
443
  ref,
@@ -428,7 +457,7 @@ var BubbleIconButton = (0, import_react5.forwardRef)(
428
457
  ...rest,
429
458
  children: [
430
459
  iconContent,
431
- badge ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "bubble-icon-button__badge", style: badgeStyles, children: typeof badge !== "boolean" ? badge : null }) : null
460
+ badge ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "bubble-icon-button__badge", style: badgeStyles, children: typeof badge !== "boolean" ? badge : null }) : null
432
461
  ]
433
462
  }
434
463
  );
@@ -436,18 +465,534 @@ var BubbleIconButton = (0, import_react5.forwardRef)(
436
465
  );
437
466
  BubbleIconButton.displayName = "BubbleIconButton";
438
467
 
439
- // src/components/BubbleArea.tsx
468
+ // src/utils/tokens.ts
469
+ var createTokenStyle = (map) => (tokens) => {
470
+ const style = {};
471
+ if (!tokens) {
472
+ return style;
473
+ }
474
+ for (const [key, value] of Object.entries(tokens)) {
475
+ if (!Object.hasOwn(map, key) || value === void 0 || value === null) {
476
+ continue;
477
+ }
478
+ const [name, unit] = map[key];
479
+ style[name] = typeof value === "number" && unit ? `${value}${unit}` : String(value);
480
+ }
481
+ return style;
482
+ };
483
+
484
+ // src/live/useBubbleLiveActivity.ts
440
485
  var import_react6 = require("react");
486
+
487
+ // src/live/controller.ts
488
+ var IDLE = {
489
+ state: "idle",
490
+ id: 0,
491
+ outcome: "neutral",
492
+ duration: 0,
493
+ paused: false,
494
+ queued: 0
495
+ };
496
+ var isDevelopment = () => globalThis.process?.env?.NODE_ENV !== "production";
497
+ var createLiveController = (initialOptions = {}) => {
498
+ const listeners = /* @__PURE__ */ new Set();
499
+ const queue = [];
500
+ let snapshot = IDLE;
501
+ let current = null;
502
+ let connections = 0;
503
+ let warned = false;
504
+ let compactSince = 0;
505
+ let expandTimer;
506
+ let closeTimer;
507
+ let remaining = 0;
508
+ let closeStartedAt = 0;
509
+ const update = (next2) => {
510
+ snapshot = { ...snapshot, ...next2, queued: queue.length };
511
+ for (const listener of listeners) {
512
+ listener();
513
+ }
514
+ };
515
+ const clearTimers = () => {
516
+ clearTimeout(expandTimer);
517
+ clearTimeout(closeTimer);
518
+ expandTimer = void 0;
519
+ closeTimer = void 0;
520
+ };
521
+ const startCloseTimer = (ms) => {
522
+ remaining = ms;
523
+ closeStartedAt = Date.now();
524
+ closeTimer = setTimeout(controller.dismiss, ms);
525
+ };
526
+ const expand = (job) => {
527
+ const { settled, options } = job;
528
+ let { content } = job;
529
+ let outcome = "neutral";
530
+ if (settled?.ok && !job.content) {
531
+ outcome = "success";
532
+ content = options.success?.(settled.value);
533
+ } else if (settled && !settled.ok) {
534
+ outcome = "error";
535
+ content = options.error?.(settled.error);
536
+ }
537
+ const duration = Math.max(0, options.duration ?? controller.options.duration ?? 4e3);
538
+ update({ state: "expanded", content, outcome, duration, paused: false });
539
+ if (duration > 0) {
540
+ startCloseTimer(duration);
541
+ }
542
+ };
543
+ const next = () => {
544
+ clearTimers();
545
+ current = queue.shift() ?? null;
546
+ if (!current) {
547
+ update({ ...IDLE, id: snapshot.id });
548
+ return;
549
+ }
550
+ if (current.settled) {
551
+ update({ id: snapshot.id + 1 });
552
+ expand(current);
553
+ return;
554
+ }
555
+ compactSince = Date.now();
556
+ update({
557
+ state: "compact",
558
+ id: snapshot.id + 1,
559
+ content: current.options.pending,
560
+ outcome: "pending",
561
+ duration: 0,
562
+ paused: false
563
+ });
564
+ };
565
+ const enqueue = (job) => {
566
+ queue.push(job);
567
+ if (!current) {
568
+ next();
569
+ } else {
570
+ update({});
571
+ }
572
+ };
573
+ const onSettled = (job) => {
574
+ if (job !== current || snapshot.state !== "compact") {
575
+ return;
576
+ }
577
+ const minPending = controller.options.minPendingMs ?? 600;
578
+ const wait = Math.max(0, minPending - (Date.now() - compactSince));
579
+ expandTimer = setTimeout(() => expand(job), wait);
580
+ };
581
+ const controller = {
582
+ options: initialOptions,
583
+ run(task, options = {}) {
584
+ if (connections === 0 && !warned && isDevelopment()) {
585
+ warned = true;
586
+ console.warn(
587
+ "useBubbleLiveActivity: the controller is not passed to any <BubbleToolbar liveActivity>; activities will not be shown."
588
+ );
589
+ }
590
+ let promise;
591
+ try {
592
+ promise = Promise.resolve(task());
593
+ } catch (error) {
594
+ promise = Promise.reject(error);
595
+ }
596
+ const job = { options, settled: null };
597
+ void (async () => {
598
+ try {
599
+ job.settled = { ok: true, value: await promise };
600
+ } catch (error) {
601
+ job.settled = { ok: false, error };
602
+ }
603
+ onSettled(job);
604
+ })();
605
+ enqueue(job);
606
+ return promise;
607
+ },
608
+ show(content, options = {}) {
609
+ enqueue({ options, settled: { ok: true, value: void 0 }, content });
610
+ },
611
+ dismiss() {
612
+ if (current) {
613
+ next();
614
+ }
615
+ },
616
+ get state() {
617
+ return snapshot.state;
618
+ },
619
+ subscribe(listener) {
620
+ listeners.add(listener);
621
+ return () => {
622
+ listeners.delete(listener);
623
+ };
624
+ },
625
+ getSnapshot: () => snapshot,
626
+ connect() {
627
+ connections += 1;
628
+ return () => {
629
+ connections -= 1;
630
+ };
631
+ },
632
+ pause() {
633
+ if (snapshot.state !== "expanded" || snapshot.paused || !closeTimer) {
634
+ return;
635
+ }
636
+ clearTimeout(closeTimer);
637
+ closeTimer = void 0;
638
+ remaining = Math.max(0, remaining - (Date.now() - closeStartedAt));
639
+ update({ paused: true });
640
+ },
641
+ resume() {
642
+ if (snapshot.state !== "expanded" || !snapshot.paused) {
643
+ return;
644
+ }
645
+ update({ paused: false });
646
+ startCloseTimer(remaining);
647
+ }
648
+ };
649
+ return controller;
650
+ };
651
+
652
+ // src/live/useBubbleLiveActivity.ts
653
+ var useBubbleLiveActivity = (options = {}) => {
654
+ const [controller] = (0, import_react6.useState)(() => createLiveController(options));
655
+ controller.options = options;
656
+ return controller;
657
+ };
658
+ var useBubbleToolbarLiveActivity = () => {
659
+ const controller = (0, import_react6.useContext)(BubbleToolbarContext).liveActivity;
660
+ if (!controller) {
661
+ throw new Error(
662
+ "useBubbleToolbarLiveActivity must be used inside a <BubbleToolbar liveActivity={\u2026}>."
663
+ );
664
+ }
665
+ return controller;
666
+ };
667
+ var asInternals = (controller) => controller;
668
+
669
+ // src/live/BubbleLiveActivity.tsx
670
+ var import_jsx_runtime3 = require("react/jsx-runtime");
671
+ var LIVE_TOKENS = {
672
+ compactWidth: ["--bubble-live-compact-width", "px"],
673
+ width: ["--bubble-live-width", "px"],
674
+ radius: ["--bubble-live-radius", "px"],
675
+ progressColor: ["--bubble-live-progress-color", ""],
676
+ successColor: ["--bubble-live-success-color", ""],
677
+ errorColor: ["--bubble-live-error-color", ""],
678
+ titleSize: ["--bubble-live-title-size", "px"],
679
+ metaSize: ["--bubble-live-meta-size", "px"],
680
+ duration: ["--bubble-live-duration", "ms"]
681
+ };
682
+ var liveTokensToStyle = createTokenStyle(LIVE_TOKENS);
683
+ var defaultLiveLabels = {
684
+ pending: "Working\u2026",
685
+ success: "Done",
686
+ error: "Something went wrong",
687
+ close: "Dismiss",
688
+ group: "Live activity"
689
+ };
690
+ var LEAVE_FALLBACK_MS = 650;
691
+ var MORPH = "clamp(0ms, var(--bubble-live-duration, 320ms), 600ms) cubic-bezier(0.16, 1, 0.3, 1)";
692
+ var VISUALLY_HIDDEN = {
693
+ position: "absolute",
694
+ width: 1,
695
+ height: 1,
696
+ margin: -1,
697
+ padding: 0,
698
+ overflow: "hidden",
699
+ clip: "rect(0 0 0 0)",
700
+ whiteSpace: "nowrap",
701
+ border: 0
702
+ };
703
+ var svg = (children, className) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
704
+ "svg",
705
+ {
706
+ "aria-hidden": "true",
707
+ focusable: "false",
708
+ className,
709
+ width: "18",
710
+ height: "18",
711
+ viewBox: "0 0 24 24",
712
+ fill: "none",
713
+ stroke: "currentColor",
714
+ strokeWidth: "2",
715
+ strokeLinecap: "round",
716
+ strokeLinejoin: "round",
717
+ children
718
+ }
719
+ );
720
+ var ICONS2 = {
721
+ pending: svg(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 12a9 9 0 1 1-6.22-8.56" }), "bubble-live__spinner"),
722
+ success: svg(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "20 6 9 17 4 12" })),
723
+ error: svg(
724
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
725
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
726
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
727
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
728
+ ] })
729
+ ),
730
+ neutral: null
731
+ };
732
+ var prefersReducedMotion = () => typeof window !== "undefined" && Boolean(window.matchMedia?.("(prefers-reduced-motion: reduce)").matches);
733
+ var LiveSlot = ({ controller, toolbarRef, children }) => {
734
+ const live = asInternals(controller);
735
+ const context = useBubbleToolbar();
736
+ const snapshot = (0, import_react7.useSyncExternalStore)(live.subscribe, live.getSnapshot, live.getSnapshot);
737
+ const [visual, setVisual] = (0, import_react7.useState)("idle");
738
+ const fromWidth = (0, import_react7.useRef)(0);
739
+ const savedFocus = (0, import_react7.useRef)(null);
740
+ const groupsRef = (0, import_react7.useRef)(null);
741
+ const liveRef = (0, import_react7.useRef)(null);
742
+ const interaction = (0, import_react7.useRef)({ hover: false, focus: false });
743
+ const active = snapshot.state !== "idle";
744
+ const labels = { ...defaultLiveLabels, ...live.options.labels };
745
+ (0, import_react7.useEffect)(() => live.connect(), [live]);
746
+ (0, import_react7.useLayoutEffect)(() => {
747
+ if (active && (visual === "idle" || visual === "leave")) {
748
+ if (visual === "idle") {
749
+ const toolbar = toolbarRef.current;
750
+ fromWidth.current = toolbar ? Math.max(0, toolbar.getBoundingClientRect().width - 8) : 0;
751
+ const focused = document.activeElement;
752
+ savedFocus.current = focused instanceof HTMLElement && groupsRef.current?.contains(focused) ? focused : null;
753
+ }
754
+ setVisual(prefersReducedMotion() || !fromWidth.current ? "live" : "enter");
755
+ } else if (!active && (visual === "enter" || visual === "live")) {
756
+ setVisual(prefersReducedMotion() || !fromWidth.current ? "idle" : "leave");
757
+ }
758
+ }, [active, visual, toolbarRef]);
759
+ (0, import_react7.useLayoutEffect)(() => {
760
+ if (groupsRef.current) {
761
+ groupsRef.current.inert = visual !== "idle";
762
+ }
763
+ }, [visual]);
764
+ (0, import_react7.useEffect)(() => {
765
+ if (visual !== "enter") {
766
+ return;
767
+ }
768
+ let second = 0;
769
+ const first = requestAnimationFrame(() => {
770
+ second = requestAnimationFrame(() => setVisual("live"));
771
+ });
772
+ return () => {
773
+ cancelAnimationFrame(first);
774
+ cancelAnimationFrame(second);
775
+ };
776
+ }, [visual]);
777
+ (0, import_react7.useEffect)(() => {
778
+ if (visual !== "leave") {
779
+ return;
780
+ }
781
+ const timer = setTimeout(() => setVisual("idle"), LEAVE_FALLBACK_MS);
782
+ return () => clearTimeout(timer);
783
+ }, [visual]);
784
+ (0, import_react7.useEffect)(() => {
785
+ if (visual === "idle") {
786
+ interaction.current = { hover: false, focus: false };
787
+ if (savedFocus.current?.isConnected) {
788
+ savedFocus.current.focus();
789
+ }
790
+ savedFocus.current = null;
791
+ } else if (savedFocus.current && !liveRef.current?.contains(document.activeElement)) {
792
+ liveRef.current?.focus();
793
+ }
794
+ }, [visual]);
795
+ const syncPause = () => {
796
+ const { hover, focus } = interaction.current;
797
+ if (hover || focus) {
798
+ live.pause();
799
+ } else {
800
+ live.resume();
801
+ }
802
+ };
803
+ const size = resolveSize(context.size);
804
+ const expanded = snapshot.state === "expanded" && visual === "live";
805
+ const { outcome } = snapshot;
806
+ const tone = snapshot.content?.tone ?? (outcome === "pending" ? "neutral" : outcome);
807
+ const title = snapshot.content?.title ?? (outcome === "pending" ? labels.pending : outcome === "error" ? labels.error : outcome === "success" ? labels.success : "");
808
+ const description = snapshot.content?.description;
809
+ const icon = snapshot.content?.icon ?? ICONS2[outcome === "pending" ? "pending" : tone];
810
+ const announcement = active ? [title, expanded ? description : ""].filter(Boolean).join(". ") : "";
811
+ const width = visual === "enter" || visual === "leave" ? `${fromWidth.current}px` : snapshot.state === "expanded" ? "min(var(--bubble-live-width, 360px), calc(100vw - 32px))" : "min(var(--bubble-live-compact-width, 200px), calc(100vw - 32px))";
812
+ const groupStyle = {
813
+ ...liveTokensToStyle(live.options.tokens),
814
+ width,
815
+ minWidth: `${size}px`,
816
+ height: snapshot.state === "expanded" ? "auto" : `${size}px`,
817
+ minHeight: `${size}px`,
818
+ padding: 0,
819
+ gap: 0,
820
+ flexDirection: "column",
821
+ alignItems: "stretch",
822
+ justifyContent: "center",
823
+ position: "relative",
824
+ overflow: "hidden",
825
+ borderRadius: snapshot.state === "expanded" ? "var(--bubble-live-radius, 24px)" : "9999px",
826
+ transition: `width ${MORPH}, border-radius ${MORPH}`
827
+ };
828
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
829
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref: groupsRef, style: { display: visual === "idle" ? "contents" : "none" }, children }),
830
+ visual === "idle" ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
831
+ BubbleGroup,
832
+ {
833
+ ref: liveRef,
834
+ className: "bubble-live",
835
+ shape: "pill",
836
+ busy: snapshot.state === "compact",
837
+ "aria-label": labels.group,
838
+ "data-state": visual === "leave" ? "leaving" : snapshot.state,
839
+ "data-tone": tone,
840
+ tabIndex: -1,
841
+ style: groupStyle,
842
+ onTransitionEnd: (event) => {
843
+ if (visual === "leave" && event.target === liveRef.current && event.propertyName === "width") {
844
+ setVisual("idle");
845
+ }
846
+ },
847
+ onMouseEnter: () => {
848
+ interaction.current.hover = true;
849
+ syncPause();
850
+ },
851
+ onMouseLeave: () => {
852
+ interaction.current.hover = false;
853
+ syncPause();
854
+ },
855
+ onFocus: () => {
856
+ interaction.current.focus = true;
857
+ syncPause();
858
+ },
859
+ onBlur: (event) => {
860
+ if (!event.currentTarget.contains(event.relatedTarget)) {
861
+ interaction.current.focus = false;
862
+ syncPause();
863
+ }
864
+ },
865
+ onKeyDown: (event) => {
866
+ if (event.key === "Escape" && snapshot.state === "expanded") {
867
+ event.preventDefault();
868
+ live.dismiss();
869
+ }
870
+ },
871
+ children: [
872
+ active ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
873
+ "div",
874
+ {
875
+ className: "bubble-live__body",
876
+ style: {
877
+ display: "flex",
878
+ alignItems: "center",
879
+ gap: 10,
880
+ minWidth: 0,
881
+ minHeight: `${size - (context.bordered ? 2 : 0)}px`,
882
+ padding: expanded ? "10px 6px 10px 14px" : "0 14px",
883
+ boxSizing: "border-box"
884
+ },
885
+ children: [
886
+ icon ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
887
+ "span",
888
+ {
889
+ className: "bubble-live__icon",
890
+ "aria-hidden": "true",
891
+ style: {
892
+ display: "inline-flex",
893
+ flexShrink: 0,
894
+ color: tone === "success" ? "var(--bubble-live-success-color, #22c55e)" : tone === "error" ? "var(--bubble-live-error-color, #ef4444)" : "inherit"
895
+ },
896
+ children: icon
897
+ }
898
+ ) : null,
899
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
900
+ "span",
901
+ {
902
+ className: "bubble-live__text",
903
+ style: { display: "flex", flexDirection: "column", flex: "1 1 auto", minWidth: 0 },
904
+ children: [
905
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
906
+ "span",
907
+ {
908
+ className: "bubble-live__title",
909
+ style: {
910
+ overflow: "hidden",
911
+ textOverflow: "ellipsis",
912
+ whiteSpace: "nowrap",
913
+ fontSize: "clamp(11px, var(--bubble-live-title-size, 13px), 18px)",
914
+ fontWeight: 600
915
+ },
916
+ children: title
917
+ }
918
+ ),
919
+ expanded && description ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
920
+ "span",
921
+ {
922
+ className: "bubble-live__description",
923
+ style: {
924
+ fontSize: "clamp(9px, var(--bubble-live-meta-size, 11px), 14px)",
925
+ lineHeight: 1.4,
926
+ opacity: 0.75,
927
+ overflowWrap: "anywhere"
928
+ },
929
+ children: description
930
+ }
931
+ ) : null
932
+ ]
933
+ }
934
+ ),
935
+ snapshot.state === "compact" && snapshot.queued > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
936
+ "span",
937
+ {
938
+ className: "bubble-live__queued",
939
+ style: { flexShrink: 0, fontSize: 11, opacity: 0.7 },
940
+ children: [
941
+ "+",
942
+ snapshot.queued
943
+ ]
944
+ }
945
+ ) : null,
946
+ expanded ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BubbleIconButton, { name: "close", ariaLabel: labels.close, onClick: live.dismiss }) : null
947
+ ]
948
+ },
949
+ snapshot.id
950
+ ) : null,
951
+ expanded && snapshot.duration > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
952
+ "span",
953
+ {
954
+ className: "bubble-live__progress",
955
+ "aria-hidden": "true",
956
+ style: {
957
+ position: "absolute",
958
+ left: 0,
959
+ right: 0,
960
+ bottom: 0,
961
+ height: 2,
962
+ animationDuration: `${snapshot.duration}ms`,
963
+ animationPlayState: snapshot.paused ? "paused" : "running"
964
+ }
965
+ },
966
+ `progress-${snapshot.id}`
967
+ ) : null
968
+ ]
969
+ }
970
+ ),
971
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
972
+ "span",
973
+ {
974
+ role: "status",
975
+ "aria-live": "polite",
976
+ className: "bubble-live__announcer",
977
+ style: VISUALLY_HIDDEN,
978
+ children: tone === "error" ? "" : announcement
979
+ }
980
+ ),
981
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { role: "alert", className: "bubble-live__announcer", style: VISUALLY_HIDDEN, children: tone === "error" ? announcement : "" })
982
+ ] });
983
+ };
984
+
985
+ // src/search/BubbleBreadcrumbSearch.tsx
986
+ var import_react11 = require("react");
987
+
988
+ // src/search/SearchBase.tsx
989
+ var import_react10 = require("react");
990
+
991
+ // src/components/BubbleArea.tsx
992
+ var import_react8 = require("react");
441
993
  var import_jsx_runtime4 = require("react/jsx-runtime");
442
- var BubbleArea = (0, import_react6.forwardRef)(
443
- ({
444
- children,
445
- minWidth = 120,
446
- maxWidth,
447
- className = "",
448
- style,
449
- ...rest
450
- }, ref) => {
994
+ var BubbleArea = (0, import_react8.forwardRef)(
995
+ ({ children, minWidth = 120, maxWidth, className = "", style, ...rest }, ref) => {
451
996
  const areaStyles = {
452
997
  display: "inline-flex",
453
998
  alignItems: "center",
@@ -458,30 +1003,636 @@ var BubbleArea = (0, import_react6.forwardRef)(
458
1003
  color: "inherit",
459
1004
  ...style
460
1005
  };
461
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1006
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref, className: `bubble-area ${className}`.trim(), style: areaStyles, ...rest, children });
1007
+ }
1008
+ );
1009
+ BubbleArea.displayName = "BubbleArea";
1010
+
1011
+ // src/search/tokens.ts
1012
+ var TOKENS = {
1013
+ width: ["--bubble-search-width", "px"],
1014
+ radius: ["--bubble-search-radius", "px"],
1015
+ divider: ["--bubble-search-divider", ""],
1016
+ focusRing: ["--bubble-search-focus-ring", ""],
1017
+ optionActiveBg: ["--bubble-search-option-active-bg", ""],
1018
+ optionRadius: ["--bubble-search-option-radius", "px"],
1019
+ titleSize: ["--bubble-search-title-size", "px"],
1020
+ titleWeight: ["--bubble-search-title-weight", ""],
1021
+ metaSize: ["--bubble-search-meta-size", "px"],
1022
+ mutedOpacity: ["--bubble-search-muted-opacity", ""],
1023
+ resultsMaxHeight: ["--bubble-search-results-max-height", "px"],
1024
+ panelPadding: ["--bubble-search-panel-padding", "px"],
1025
+ duration: ["--bubble-search-duration", "ms"],
1026
+ easing: ["--bubble-search-easing", ""],
1027
+ breadcrumbSize: ["--bubble-breadcrumb-size", "px"],
1028
+ breadcrumbSeparatorColor: ["--bubble-breadcrumb-separator-color", ""],
1029
+ breadcrumbCurrentWeight: ["--bubble-breadcrumb-current-weight", ""],
1030
+ breadcrumbMutedOpacity: ["--bubble-breadcrumb-muted-opacity", ""]
1031
+ };
1032
+ var tokensToStyle = createTokenStyle(TOKENS);
1033
+
1034
+ // src/search/useBubbleSearch.ts
1035
+ var import_react9 = require("react");
1036
+ var normalizeQuery = (query) => query.trim().toLowerCase();
1037
+ var useLatest = (value) => {
1038
+ const ref = (0, import_react9.useRef)(value);
1039
+ ref.current = value;
1040
+ return ref;
1041
+ };
1042
+ function useBubbleSearch({
1043
+ open,
1044
+ setOpen,
1045
+ groupRef,
1046
+ onSearch,
1047
+ onSelect,
1048
+ onError,
1049
+ suggestions = [],
1050
+ debounceMs
1051
+ }) {
1052
+ const { run } = useBubbleActivity();
1053
+ const [query, setQuery] = (0, import_react9.useState)("");
1054
+ const [result, setResult] = (0, import_react9.useState)(null);
1055
+ const [activeIndex, setActiveIndex] = (0, import_react9.useState)(-1);
1056
+ const inputRef = (0, import_react9.useRef)(null);
1057
+ const buttonRef = (0, import_react9.useRef)(null);
1058
+ const listId = (0, import_react9.useId)();
1059
+ const hintId = (0, import_react9.useId)();
1060
+ const onSearchRef = useLatest(onSearch);
1061
+ const onSelectRef = useLatest(onSelect);
1062
+ const onErrorRef = useLatest(onError);
1063
+ const normalizedQuery = normalizeQuery(query);
1064
+ const pending = Boolean(normalizedQuery && result?.query !== normalizedQuery);
1065
+ const noResults = Boolean(normalizedQuery && !pending && result?.items.length === 0);
1066
+ const options = pending ? [] : normalizedQuery && !noResults ? result?.items ?? [] : suggestions;
1067
+ const optionId = (index) => `${listId}-option-${index}`;
1068
+ const close = (0, import_react9.useCallback)(
1069
+ (restoreFocus = false) => {
1070
+ setOpen(false);
1071
+ if (restoreFocus) {
1072
+ buttonRef.current?.focus();
1073
+ }
1074
+ },
1075
+ [setOpen]
1076
+ );
1077
+ (0, import_react9.useEffect)(() => {
1078
+ if (open) {
1079
+ inputRef.current?.focus();
1080
+ } else {
1081
+ setQuery("");
1082
+ setResult(null);
1083
+ setActiveIndex(-1);
1084
+ }
1085
+ }, [open]);
1086
+ (0, import_react9.useEffect)(() => {
1087
+ if (!open) {
1088
+ return;
1089
+ }
1090
+ const handlePointerDown = (event) => {
1091
+ if (event.target instanceof Node && !groupRef.current?.contains(event.target)) {
1092
+ close();
1093
+ }
1094
+ };
1095
+ document.addEventListener("pointerdown", handlePointerDown);
1096
+ return () => document.removeEventListener("pointerdown", handlePointerDown);
1097
+ }, [open, close, groupRef]);
1098
+ (0, import_react9.useEffect)(() => {
1099
+ if (!open || !normalizedQuery) {
1100
+ return;
1101
+ }
1102
+ const controller = new AbortController();
1103
+ const search = async () => {
1104
+ try {
1105
+ const items = await run(() => onSearchRef.current(normalizedQuery, controller.signal));
1106
+ if (!controller.signal.aborted) {
1107
+ setResult({ query: normalizedQuery, items });
1108
+ }
1109
+ } catch (error) {
1110
+ if (!controller.signal.aborted) {
1111
+ onErrorRef.current?.(error);
1112
+ setResult({ query: normalizedQuery, items: [] });
1113
+ }
1114
+ }
1115
+ };
1116
+ if (debounceMs > 0) {
1117
+ const timer = setTimeout(() => void search(), debounceMs);
1118
+ return () => {
1119
+ clearTimeout(timer);
1120
+ controller.abort();
1121
+ };
1122
+ }
1123
+ void search();
1124
+ return () => controller.abort();
1125
+ }, [open, normalizedQuery, run, debounceMs, onSearchRef, onErrorRef]);
1126
+ (0, import_react9.useEffect)(() => {
1127
+ if (activeIndex >= 0) {
1128
+ document.getElementById(listId)?.children[activeIndex]?.scrollIntoView?.({ block: "nearest" });
1129
+ }
1130
+ }, [activeIndex, listId]);
1131
+ const select = (item) => {
1132
+ onSelectRef.current(item);
1133
+ close(true);
1134
+ };
1135
+ const changeQuery = (value) => {
1136
+ setQuery(value);
1137
+ if (normalizeQuery(value) !== normalizedQuery) {
1138
+ setResult(null);
1139
+ }
1140
+ setActiveIndex(-1);
1141
+ };
1142
+ const handleInputKeyDown = (event) => {
1143
+ if (event.key === "Escape") {
1144
+ event.preventDefault();
1145
+ event.stopPropagation();
1146
+ close(true);
1147
+ } else if ((event.key === "ArrowDown" || event.key === "ArrowUp") && options.length) {
1148
+ event.preventDefault();
1149
+ const direction = event.key === "ArrowDown" ? 1 : -1;
1150
+ setActiveIndex(
1151
+ (index) => index < 0 ? direction === 1 ? 0 : options.length - 1 : (index + direction + options.length) % options.length
1152
+ );
1153
+ } else if (event.key === "Enter" && options[activeIndex]) {
1154
+ event.preventDefault();
1155
+ select(options[activeIndex]);
1156
+ }
1157
+ };
1158
+ return {
1159
+ query,
1160
+ changeQuery,
1161
+ normalizedQuery,
1162
+ pending,
1163
+ noResults,
1164
+ options,
1165
+ activeIndex,
1166
+ setActiveIndex,
1167
+ activeDescendant: activeIndex >= 0 && options[activeIndex] ? optionId(activeIndex) : void 0,
1168
+ optionId,
1169
+ listId,
1170
+ hintId,
1171
+ inputRef,
1172
+ buttonRef,
1173
+ close,
1174
+ select,
1175
+ handleInputKeyDown
1176
+ };
1177
+ }
1178
+
1179
+ // src/search/SearchBase.tsx
1180
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1181
+ var defaultSearchLabels = {
1182
+ group: "Search",
1183
+ button: "Search",
1184
+ input: "Search",
1185
+ listbox: "Results",
1186
+ placeholder: "Search\u2026",
1187
+ searching: "Searching\u2026",
1188
+ noResults: "No results. Try another term or explore a suggestion.",
1189
+ suggestions: "Suggestions",
1190
+ results: (count) => `${count} ${count === 1 ? "result" : "results"}`,
1191
+ hint: "\u2191 \u2193 to explore \xB7 Enter to select \xB7 Esc to close"
1192
+ };
1193
+ var mergeRefs = (...refs) => (value) => {
1194
+ for (const ref of refs) {
1195
+ if (typeof ref === "function") {
1196
+ ref(value);
1197
+ } else if (ref) {
1198
+ ref.current = value;
1199
+ }
1200
+ }
1201
+ };
1202
+ var SearchBody = ({
1203
+ open,
1204
+ setOpen,
1205
+ groupRef,
1206
+ headerHeight,
1207
+ upward,
1208
+ labels,
1209
+ idleContent,
1210
+ onSearch,
1211
+ onSelect,
1212
+ onError,
1213
+ suggestions,
1214
+ debounceMs = 0
1215
+ }) => {
1216
+ const search = useBubbleSearch({
1217
+ open,
1218
+ setOpen,
1219
+ groupRef,
1220
+ onSearch,
1221
+ onSelect,
1222
+ onError,
1223
+ suggestions,
1224
+ debounceMs
1225
+ });
1226
+ const { options, activeIndex, listId, hintId } = search;
1227
+ const compact = !open && idleContent === void 0;
1228
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1229
+ "div",
1230
+ {
1231
+ className: "bubble-search__body",
1232
+ "data-placement": upward ? "top" : "bottom",
1233
+ style: { display: "flex", flexDirection: upward ? "column-reverse" : "column", minWidth: 0 },
1234
+ onBlurCapture: (event) => {
1235
+ if (!event.currentTarget.contains(event.relatedTarget)) {
1236
+ search.close();
1237
+ }
1238
+ },
1239
+ children: [
1240
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1241
+ "div",
1242
+ {
1243
+ className: "bubble-search__header",
1244
+ "data-compact": compact,
1245
+ style: { height: `${headerHeight}px` },
1246
+ children: [
1247
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1248
+ BubbleIconButton,
1249
+ {
1250
+ ref: search.buttonRef,
1251
+ name: "search",
1252
+ ariaLabel: labels.button,
1253
+ "aria-expanded": open,
1254
+ "aria-controls": open ? listId : void 0,
1255
+ active: open,
1256
+ onKeyDown: (event) => {
1257
+ if (event.key === "Escape" && open) {
1258
+ event.preventDefault();
1259
+ search.close(true);
1260
+ }
1261
+ },
1262
+ onClick: () => {
1263
+ if (!open) {
1264
+ setOpen(true);
1265
+ } else {
1266
+ search.inputRef.current?.focus();
1267
+ }
1268
+ }
1269
+ }
1270
+ ),
1271
+ compact ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(BubbleArea, { minWidth: 0, className: "bubble-search__area", children: open ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1272
+ "input",
1273
+ {
1274
+ ref: search.inputRef,
1275
+ className: "bubble-search__input",
1276
+ type: "text",
1277
+ role: "combobox",
1278
+ "aria-label": labels.input,
1279
+ "aria-autocomplete": "list",
1280
+ "aria-expanded": open,
1281
+ "aria-controls": listId,
1282
+ "aria-describedby": hintId,
1283
+ "aria-activedescendant": search.activeDescendant,
1284
+ autoComplete: "off",
1285
+ placeholder: labels.placeholder,
1286
+ value: search.query,
1287
+ onChange: (event) => search.changeQuery(event.target.value),
1288
+ onKeyDown: search.handleInputKeyDown
1289
+ }
1290
+ ) : idleContent })
1291
+ ]
1292
+ }
1293
+ ),
1294
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1295
+ "div",
1296
+ {
1297
+ className: "bubble-search__expansion",
1298
+ "data-open": open,
1299
+ "aria-hidden": !open,
1300
+ style: {
1301
+ display: "grid",
1302
+ gridTemplateRows: open ? "1fr" : "0fr",
1303
+ visibility: open ? "visible" : "hidden",
1304
+ opacity: open ? 1 : 0
1305
+ },
1306
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1307
+ "div",
1308
+ {
1309
+ className: "bubble-search__expansion-inner",
1310
+ style: { minHeight: 0, overflow: "hidden" },
1311
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "bubble-search__panel", children: [
1312
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "bubble-search__status", role: "status", "aria-live": "polite", children: search.pending ? labels.searching : search.noResults ? labels.noResults : search.normalizedQuery ? labels.results(options.length) : labels.suggestions }),
1313
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1314
+ "div",
1315
+ {
1316
+ id: listId,
1317
+ role: "listbox",
1318
+ "aria-label": labels.listbox,
1319
+ className: "bubble-search__results",
1320
+ children: options.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1321
+ "div",
1322
+ {
1323
+ id: search.optionId(index),
1324
+ role: "option",
1325
+ tabIndex: -1,
1326
+ "aria-selected": activeIndex === index,
1327
+ className: "bubble-search__option",
1328
+ onPointerDown: (event) => event.preventDefault(),
1329
+ onMouseMove: () => search.setActiveIndex(index),
1330
+ onClick: () => search.select(item),
1331
+ onKeyDown: (event) => {
1332
+ if (event.key === "Enter" || event.key === " ") {
1333
+ event.preventDefault();
1334
+ search.select(item);
1335
+ }
1336
+ },
1337
+ children: [
1338
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "bubble-search__option-title", children: item.title }),
1339
+ item.description ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "bubble-search__option-description", children: item.description }) : null
1340
+ ]
1341
+ },
1342
+ item.id
1343
+ ))
1344
+ }
1345
+ ),
1346
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { id: hintId, className: "bubble-search__hint", children: labels.hint })
1347
+ ] })
1348
+ }
1349
+ )
1350
+ }
1351
+ )
1352
+ ]
1353
+ }
1354
+ );
1355
+ };
1356
+ var SearchBase = ({
1357
+ open: openProp,
1358
+ defaultOpen = false,
1359
+ onOpenChange,
1360
+ tokens,
1361
+ className = "",
1362
+ forwardedRef,
1363
+ ...bodyProps
1364
+ }) => {
1365
+ const context = useBubbleToolbar();
1366
+ const groupRef = (0, import_react10.useRef)(null);
1367
+ const [innerOpen, setInnerOpen] = (0, import_react10.useState)(defaultOpen);
1368
+ const controlled = openProp !== void 0;
1369
+ const open = controlled ? openProp : innerOpen;
1370
+ const onOpenChangeRef = (0, import_react10.useRef)(onOpenChange);
1371
+ onOpenChangeRef.current = onOpenChange;
1372
+ const setOpen = (0, import_react10.useCallback)(
1373
+ (next) => {
1374
+ if (!controlled) {
1375
+ setInnerOpen(next);
1376
+ }
1377
+ onOpenChangeRef.current?.(next);
1378
+ },
1379
+ [controlled]
1380
+ );
1381
+ const ref = (0, import_react10.useMemo)(() => mergeRefs(groupRef, forwardedRef), [forwardedRef]);
1382
+ const size = resolveSize(context.size);
1383
+ const compact = !open && bodyProps.idleContent === void 0;
1384
+ const style = {
1385
+ ...tokensToStyle(tokens),
1386
+ height: "auto",
1387
+ width: compact ? `${size}px` : "min(var(--bubble-search-width, 360px), calc(100vw - 96px))",
1388
+ minWidth: 0,
1389
+ padding: 0,
1390
+ gap: 0,
1391
+ flexDirection: "column",
1392
+ alignItems: "stretch",
1393
+ borderRadius: "var(--bubble-search-radius, 24px)"
1394
+ };
1395
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1396
+ BubbleGroup,
1397
+ {
1398
+ ref,
1399
+ "aria-label": bodyProps.labels.group,
1400
+ shape: "pill",
1401
+ className: `bubble-search ${className}`.trim(),
1402
+ "data-expanded": open,
1403
+ "data-compact": compact,
1404
+ style,
1405
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1406
+ SearchBody,
1407
+ {
1408
+ ...bodyProps,
1409
+ open,
1410
+ setOpen,
1411
+ groupRef,
1412
+ headerHeight: size - (context.bordered ? 2 : 0),
1413
+ upward: context.position === "bottom"
1414
+ }
1415
+ )
1416
+ }
1417
+ );
1418
+ };
1419
+
1420
+ // src/search/BubbleBreadcrumbSearch.tsx
1421
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1422
+ var BreadcrumbLevel = ({ item, current }) => item.href ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { href: item.href, "aria-current": current ? "page" : void 0, children: item.label }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { "aria-current": current ? "page" : void 0, children: item.label });
1423
+ var Breadcrumb = ({
1424
+ items,
1425
+ separator,
1426
+ label
1427
+ }) => {
1428
+ const parents = items.slice(0, -1);
1429
+ const current = items[items.length - 1];
1430
+ const divider = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "bubble-breadcrumb__separator", "aria-hidden": "true", children: ` ${separator} ` });
1431
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1432
+ "nav",
1433
+ {
1434
+ "aria-label": label,
1435
+ className: "bubble-breadcrumb",
1436
+ title: items.map((item) => item.label).join(` ${separator} `),
1437
+ children: [
1438
+ parents.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1439
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "bubble-breadcrumb__parents", children: parents.map((item, index) => (
1440
+ // biome-ignore lint/suspicious/noArrayIndexKey: Breadcrumb levels are identified by position.
1441
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
1442
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BreadcrumbLevel, { item, current: false }),
1443
+ index < parents.length - 1 ? divider : null
1444
+ ] }, index)
1445
+ )) }),
1446
+ divider
1447
+ ] }) : null,
1448
+ current ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "bubble-breadcrumb__current", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BreadcrumbLevel, { item: current, current: true }) }) : null
1449
+ ]
1450
+ }
1451
+ );
1452
+ };
1453
+ var BubbleBreadcrumbSearchInner = (0, import_react11.forwardRef)(
1454
+ ({ items, separator = "/", labels, ...props }, ref) => {
1455
+ const { location = "Breadcrumb", ...searchLabels } = labels ?? {};
1456
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1457
+ SearchBase,
1458
+ {
1459
+ ...props,
1460
+ labels: { ...defaultSearchLabels, ...searchLabels },
1461
+ idleContent: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Breadcrumb, { items, separator, label: location }),
1462
+ forwardedRef: ref
1463
+ }
1464
+ );
1465
+ }
1466
+ );
1467
+ BubbleBreadcrumbSearchInner.displayName = "BubbleBreadcrumbSearch";
1468
+ var BubbleBreadcrumbSearch = BubbleBreadcrumbSearchInner;
1469
+
1470
+ // src/search/BubbleSearch.tsx
1471
+ var import_react12 = require("react");
1472
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1473
+ var BubbleSearchInner = (0, import_react12.forwardRef)(
1474
+ ({ labels, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SearchBase, { ...props, labels: { ...defaultSearchLabels, ...labels }, forwardedRef: ref })
1475
+ );
1476
+ BubbleSearchInner.displayName = "BubbleSearch";
1477
+ var BubbleSearch = BubbleSearchInner;
1478
+
1479
+ // src/BubbleToolbar.tsx
1480
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1481
+ var BubbleToolbar = (0, import_react13.forwardRef)(
1482
+ ({
1483
+ groups,
1484
+ children,
1485
+ position = "bottom",
1486
+ theme = "glass",
1487
+ size = "md",
1488
+ blurred = true,
1489
+ blurAmount = 16,
1490
+ groupGap = 8,
1491
+ itemGap = 8,
1492
+ bordered = true,
1493
+ shadow = true,
1494
+ busy = false,
1495
+ activityColors,
1496
+ activityDuration,
1497
+ className = "",
1498
+ style,
1499
+ ariaLabel = "Toolbar",
1500
+ liveActivity,
1501
+ ...rest
1502
+ }, ref) => {
1503
+ const contextValue = (0, import_react13.useMemo)(
1504
+ () => ({
1505
+ size,
1506
+ theme,
1507
+ blurred,
1508
+ blurAmount,
1509
+ itemGap,
1510
+ bordered,
1511
+ shadow,
1512
+ position,
1513
+ liveActivity,
1514
+ busy,
1515
+ activityColors,
1516
+ activityDuration
1517
+ }),
1518
+ [
1519
+ size,
1520
+ theme,
1521
+ blurred,
1522
+ blurAmount,
1523
+ itemGap,
1524
+ bordered,
1525
+ shadow,
1526
+ position,
1527
+ liveActivity,
1528
+ busy,
1529
+ activityColors,
1530
+ activityDuration
1531
+ ]
1532
+ );
1533
+ const toolbarRef = (0, import_react13.useRef)(null);
1534
+ const setRef = (0, import_react13.useCallback)(
1535
+ (node) => {
1536
+ toolbarRef.current = node;
1537
+ if (typeof ref === "function") {
1538
+ ref(node);
1539
+ } else if (ref) {
1540
+ ref.current = node;
1541
+ }
1542
+ },
1543
+ [ref]
1544
+ );
1545
+ const getPositionStyles = () => {
1546
+ switch (position) {
1547
+ case "bottom":
1548
+ return {
1549
+ position: "fixed",
1550
+ bottom: "24px",
1551
+ left: "50%",
1552
+ transform: "translateX(-50%)",
1553
+ zIndex: 1e3
1554
+ };
1555
+ case "top":
1556
+ return {
1557
+ position: "fixed",
1558
+ top: "24px",
1559
+ left: "50%",
1560
+ transform: "translateX(-50%)",
1561
+ zIndex: 1e3
1562
+ };
1563
+ case "static":
1564
+ return {
1565
+ position: "relative"
1566
+ };
1567
+ default:
1568
+ return {};
1569
+ }
1570
+ };
1571
+ const containerStyles = {
1572
+ ...getPositionStyles(),
1573
+ display: "inline-flex",
1574
+ alignItems: "center",
1575
+ gap: formatPx(groupGap),
1576
+ maxWidth: "calc(100vw - 32px)",
1577
+ overflowX: "auto",
1578
+ overflowY: "hidden",
1579
+ padding: "4px",
1580
+ boxSizing: "border-box",
1581
+ scrollbarWidth: "none",
1582
+ // Firefox
1583
+ msOverflowStyle: "none",
1584
+ // IE/Edge
1585
+ WebkitOverflowScrolling: "touch",
1586
+ pointerEvents: "auto",
1587
+ ...style
1588
+ };
1589
+ const combinedClassName = [
1590
+ "bubble-toolbar",
1591
+ `bubble-toolbar--${position}`,
1592
+ `bubble-toolbar--${theme}`,
1593
+ className
1594
+ ].filter(Boolean).join(" ");
1595
+ const content = /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1596
+ groups && groups.length > 0 ? groups.map((groupItems, index) => (
1597
+ // biome-ignore lint/suspicious/noArrayIndexKey: The groups array API identifies groups by position; use compound children for keyed groups.
1598
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(BubbleGroup, { children: groupItems }, index)
1599
+ )) : null,
1600
+ children
1601
+ ] });
1602
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(BubbleToolbarContext.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
462
1603
  "div",
463
1604
  {
464
- ref,
465
- className: `bubble-area ${className}`.trim(),
466
- style: areaStyles,
1605
+ ref: setRef,
1606
+ role: "toolbar",
1607
+ "aria-label": ariaLabel,
1608
+ className: combinedClassName,
1609
+ style: containerStyles,
467
1610
  ...rest,
468
- children
1611
+ children: liveActivity ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LiveSlot, { controller: liveActivity, toolbarRef, children: content }) : content
469
1612
  }
470
- );
1613
+ ) });
471
1614
  }
472
1615
  );
473
- BubbleArea.displayName = "BubbleArea";
1616
+ BubbleToolbar.displayName = "BubbleToolbar";
1617
+ BubbleToolbar.Group = BubbleGroup;
1618
+ BubbleToolbar.Search = BubbleSearch;
1619
+ BubbleToolbar.BreadcrumbSearch = BubbleBreadcrumbSearch;
474
1620
  // Annotate the CommonJS export names for ESM import in node:
475
1621
  0 && (module.exports = {
476
1622
  BubbleArea,
1623
+ BubbleBreadcrumbSearch,
477
1624
  BubbleGroup,
478
1625
  BubbleIconButton,
1626
+ BubbleSearch,
479
1627
  BubbleToolbar,
480
1628
  BubbleToolbarContext,
481
1629
  formatPx,
482
1630
  getGlassStyles,
483
1631
  getValidChildren,
484
1632
  resolveSize,
485
- useBubbleToolbar
1633
+ useBubbleActivity,
1634
+ useBubbleLiveActivity,
1635
+ useBubbleToolbar,
1636
+ useBubbleToolbarLiveActivity
486
1637
  });
487
1638
  //# sourceMappingURL=index.js.map