@bud.sh/app 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/client/assets/bud/design.js +1 -1
  2. package/dist/client/assets/{canvas-DIKX_HCJ.js → canvas-8GXJIuDK.js} +1 -1
  3. package/dist/client/assets/difference-worker-C99ychYw.js +8 -0
  4. package/dist/client/assets/{edit-B050x0rV.js → edit-DUotSxjS.js} +12 -12
  5. package/dist/client/assets/files-DkWt1l9H.js +1 -0
  6. package/dist/client/assets/i18n-hH_Ba5QO.js +1 -0
  7. package/dist/client/assets/{index-COvyj5zV.js → index-Dn2pfmXf.js} +2 -2
  8. package/dist/client/assets/index-tpHYVXma.css +2 -0
  9. package/dist/client/assets/lru-DzKYIXMA.js +1 -0
  10. package/dist/client/assets/pages-BVd6MQmu.js +1 -0
  11. package/dist/client/assets/{project-branches-DwKjCwZv.js → project-branches-CMGRmrqr.js} +1 -1
  12. package/dist/client/assets/project-comments-D1IIQwsW.js +1 -0
  13. package/dist/client/assets/project-thread-links-DKwe_SYb.js +1 -0
  14. package/dist/client/assets/{project-threads-DeKXNqeP.js → project-threads-ZbKDh78d.js} +105 -105
  15. package/dist/client/assets/{project-work-B7PGQtV9.js → project-work-D4DZqBCZ.js} +1 -1
  16. package/dist/client/assets/rolldown-runtime-XKyaHtl9.js +1 -0
  17. package/dist/client/assets/task-design-BPpc4Gd7.js +23 -0
  18. package/dist/client/assets/tasks-Cn0VOZ9Z.js +1 -0
  19. package/dist/client/assets/{tasks-request-CsUB5__X.js → tasks-request-BLyOzokZ.js} +1 -1
  20. package/dist/client/assets/workspace-DGErsSg6.js +2 -0
  21. package/dist/client/index.html +3 -3
  22. package/dist/hosted/assets/{canvas-gWj_ocKK.js → canvas-BYTOp1p7.js} +6 -4
  23. package/dist/hosted/assets/{comment-data-C-rNCB1n.js → comment-data-Cn5y2VxV.js} +4026 -3665
  24. package/dist/hosted/assets/{files-CAPvbavY.js → files-B1P2_24q.js} +5 -3
  25. package/dist/hosted/assets/{pages-DJiOvT-l.js → pages-B0ZlVdci.js} +3 -3
  26. package/dist/hosted/assets/{project-branches-BgrfdvtL.js → project-branches-DlCWmfRo.js} +1 -1
  27. package/dist/hosted/assets/{project-thread-links-B7uzThL9.js → project-thread-links-BZXQ200n.js} +2 -2
  28. package/dist/hosted/assets/{screen-D4D9nh4V.js → screen-CfdC3NbR.js} +72 -137
  29. package/dist/hosted/assets/{tasks-C7aGLzBw.js → tasks-_yKh2Z6q.js} +12 -5
  30. package/dist/hosted/assets/{tasks-request-CikFdCgP.js → tasks-request-CFPLvLc7.js} +1 -1
  31. package/dist/hosted/hosted.js +164 -16
  32. package/dist/node/assets/{src-BdvZ2mYc.js → src-CerrKjID.js} +57 -1
  33. package/dist/node/design.js +826 -791
  34. package/dist/node/server.js +25 -3
  35. package/dist/node-host/assets/{canvas-kXzp80a5.js → canvas-DxmcFZCh.js} +6 -4
  36. package/dist/node-host/assets/{comment-data-CeuJbJKJ.js → comment-data-yfWEdDQL.js} +4004 -3643
  37. package/dist/node-host/assets/{files-0YIuvKzb.js → files-anHi-apm.js} +5 -3
  38. package/dist/node-host/assets/{pages-DOF22SyG.js → pages-CW4aF6dK.js} +3 -3
  39. package/dist/node-host/assets/{project-branches-BeEwMBIw.js → project-branches-D7rVCxQX.js} +1 -1
  40. package/dist/node-host/assets/{project-thread-links-Bs_MtfBC.js → project-thread-links-m-OLpsD9.js} +2 -2
  41. package/dist/node-host/assets/{screen-DRTAKkn3.js → screen-DapOVrga.js} +72 -137
  42. package/dist/node-host/assets/{tasks-BA_hcbj5.js → tasks-BBK_CybR.js} +12 -5
  43. package/dist/node-host/assets/{tasks-request-Doc9y_20.js → tasks-request-B0aeNocI.js} +1 -1
  44. package/dist/node-host/node-host.js +164 -16
  45. package/dist/server/assets/{canvas-CiN6jNIZ.js → canvas-CkMK4hoN.js} +7 -5
  46. package/dist/server/assets/{comment-data-9SqALQjT.js → comment-data-NLeHG89b.js} +3781 -3631
  47. package/dist/server/assets/{events-6z5v7Uca.js → events-iHpLGr3k.js} +213 -2
  48. package/dist/server/assets/{files-DBVqeLdL.js → files-CRdl489E.js} +6 -4
  49. package/dist/server/assets/{listener-DGWKUc2o.js → listener-BAYn9Q-u.js} +35 -9
  50. package/dist/server/assets/{pages-DJMLr6j9.js → pages-DXVlJlBC.js} +4 -4
  51. package/dist/server/assets/{project-branches-DbKEmiQT.js → project-branches-CwTsf6WQ.js} +2 -2
  52. package/dist/server/assets/{project-thread-links-C0kV_Pcl.js → project-thread-links-lLS_rq7c.js} +3 -3
  53. package/dist/server/assets/{project-work-C0FN3w5Y.js → project-work-CSZlCwH-.js} +3 -3
  54. package/dist/server/assets/{screen-BtvzEiIa.js → screen-B1zH_1O3.js} +73 -138
  55. package/dist/server/assets/{server-CYvuOSBA.js → server-DxK-UB_Z.js} +30 -4
  56. package/dist/server/assets/{services-DK4fqrlF.js → services-BRrgXYlT.js} +105 -9
  57. package/dist/server/assets/{tasks-DX71x8f9.js → tasks-CGdz-uox.js} +14 -7
  58. package/dist/server/assets/{tasks-request-C3jk9Jyu.js → tasks-request-D2NBjCTl.js} +2 -2
  59. package/dist/server/migration.js +3 -3
  60. package/dist/server/runtime.js +3 -3
  61. package/dist/server/server.js +2 -2
  62. package/dist/types/server.d.ts +11 -1
  63. package/package.json +1 -1
  64. package/dist/client/assets/difference-worker-CwQNxTJL.js +0 -8
  65. package/dist/client/assets/files-BQ9O3Z8-.js +0 -1
  66. package/dist/client/assets/i18n-CL0jBP7Y.js +0 -1
  67. package/dist/client/assets/index-CjhJtepP.css +0 -2
  68. package/dist/client/assets/lru-BkhKJhvt.js +0 -1
  69. package/dist/client/assets/pages-BGl2Yuza.js +0 -1
  70. package/dist/client/assets/project-comments-Cdip2Y10.js +0 -1
  71. package/dist/client/assets/project-thread-links-BGIkGQeU.js +0 -1
  72. package/dist/client/assets/task-design-DJp5yho1.js +0 -23
  73. package/dist/client/assets/tasks-BHXj727C.js +0 -1
  74. package/dist/client/assets/workspace-CbO7RsI2.js +0 -2
@@ -1,5 +1,5 @@
1
1
  import { a as FieldCardFooter, c as Button, i as FieldCardContent, l as Text, o as FieldCardHeader, r as FieldCard, s as ariaAttributes, t as Loader } from "./assets/loader-QRRDmKqp.js";
2
- import { D as statusCategory, S as valueByRole, T as fieldByRole, _ as parseWorkPatch, f as parseWorkCreate, h as parseWorkItemRef } from "./assets/src-BdvZ2mYc.js";
2
+ import { C as valueByRole, g as parseWorkItemRef, j as statusCategory, k as fieldByRole, p as parseWorkCreate, v as parseWorkPatch } from "./assets/src-CerrKjID.js";
3
3
  import { createElement, on, ref } from "remix/component";
4
4
  import { Fragment, jsx, jsxs } from "remix/component/jsx-runtime";
5
5
  import { createMatcher } from "remix/route-pattern/match";
@@ -3230,6 +3230,10 @@ var de = {
3230
3230
  text: "Bud hat eine Kommentaränderung rückgängig gemacht",
3231
3231
  source: "1f7e1986"
3232
3232
  },
3233
+ "app.client.projectComments.commentDeleted": {
3234
+ text: "Kommentar gelöscht",
3235
+ source: "e442d3f3"
3236
+ },
3233
3237
  "app.client.projectComments.commentSaved": {
3234
3238
  text: "Kommentar gespeichert",
3235
3239
  source: "f7901519"
@@ -3250,6 +3254,10 @@ var de = {
3250
3254
  text: "Die Diskussion konnte nicht geladen werden.",
3251
3255
  source: "bac34014"
3252
3256
  },
3257
+ "app.client.projectComments.discussionDeleted": {
3258
+ text: "Diskussion gelöscht",
3259
+ source: "156ebf8c"
3260
+ },
3253
3261
  "app.client.projectComments.discussionNoLongerInView": {
3254
3262
  text: "Diese Diskussion gehört nicht mehr zu dieser Ansicht.",
3255
3263
  source: "12e3b200"
@@ -3845,10 +3853,6 @@ var de = {
3845
3853
  text: "Leinwand verschieben",
3846
3854
  source: "233eddf0"
3847
3855
  },
3848
- "canvas.canvasWorkspace.reload": {
3849
- text: "Neu laden",
3850
- source: "1a43a2ac"
3851
- },
3852
3856
  "canvas.canvasWorkspace.savingComments": {
3853
3857
  text: "Kommentare werden gespeichert",
3854
3858
  source: "1f15fbfb"
@@ -4053,6 +4057,30 @@ var de = {
4053
4057
  text: "Kommentar",
4054
4058
  source: "984f9ffe"
4055
4059
  },
4060
+ "comments.commentThread.commentActions": {
4061
+ text: "Kommentaraktionen",
4062
+ source: "ebed7451"
4063
+ },
4064
+ "comments.commentThread.confirmDeleteComment": {
4065
+ text: "Diesen Kommentar löschen?",
4066
+ source: "46eacc70"
4067
+ },
4068
+ "comments.commentThread.confirmDeleteDiscussion": {
4069
+ text: "Diese Diskussion löschen?",
4070
+ source: "860d22bf"
4071
+ },
4072
+ "comments.commentThread.delete": {
4073
+ text: "Löschen",
4074
+ source: "5797ea6a"
4075
+ },
4076
+ "comments.commentThread.deleteComment": {
4077
+ text: "Kommentar löschen",
4078
+ source: "d8c741d3"
4079
+ },
4080
+ "comments.commentThread.deleteDiscussion": {
4081
+ text: "Diskussion löschen",
4082
+ source: "af680606"
4083
+ },
4056
4084
  "comments.commentThread.leaveCommentPlaceholder": {
4057
4085
  text: "Kommentar schreiben…",
4058
4086
  source: "98a1ef74"
@@ -7751,11 +7779,13 @@ var en = {
7751
7779
  "app.client.projectComments.adapterDoesNotSupportOperation": "The comment adapter does not support this operation.",
7752
7780
  "app.client.projectComments.adapterUnavailable": "The comment adapter is unavailable.",
7753
7781
  "app.client.projectComments.budUndidCommentChange": "Bud undid a comment change",
7782
+ "app.client.projectComments.commentDeleted": "Comment deleted",
7754
7783
  "app.client.projectComments.commentSaved": "Comment saved",
7755
7784
  "app.client.projectComments.commentWriteNotConfirmed": "The comment write was not confirmed.",
7756
7785
  "app.client.projectComments.commentsSynced": "Comments synced",
7757
7786
  "app.client.projectComments.couldNotLoadComments": "Could not load comments.",
7758
7787
  "app.client.projectComments.couldNotLoadDiscussion": "Could not load the discussion.",
7788
+ "app.client.projectComments.discussionDeleted": "Discussion deleted",
7759
7789
  "app.client.projectComments.discussionNoLongerInView": "This discussion is no longer part of this view.",
7760
7790
  "app.client.projectComments.loadCommentsBeforeSaving": "Load comments before saving.",
7761
7791
  "app.client.projectComments.refreshBeforeTryingAgain": "The comment write could not be confirmed. Refresh before trying again.",
@@ -7916,7 +7946,6 @@ var en = {
7916
7946
  "canvas.canvasWorkspace.interactWithPages": "Interact with pages",
7917
7947
  "canvas.canvasWorkspace.newComment": "New comment",
7918
7948
  "canvas.canvasWorkspace.panCanvas": "Pan canvas",
7919
- "canvas.canvasWorkspace.reload": "Reload",
7920
7949
  "canvas.canvasWorkspace.savingComments": "Saving comments",
7921
7950
  "canvas.canvasWorkspace.showDarkPages": "Show dark pages",
7922
7951
  "canvas.canvasWorkspace.showFullPages": "Show full pages",
@@ -7977,6 +8006,12 @@ var en = {
7977
8006
  "comments.commentPins.detachedTitle": "{name} · Its element is no longer on the page",
7978
8007
  "comments.commentThread.cancel": "Cancel",
7979
8008
  "comments.commentThread.comment": "Comment",
8009
+ "comments.commentThread.commentActions": "Comment actions",
8010
+ "comments.commentThread.confirmDeleteComment": "Delete this comment?",
8011
+ "comments.commentThread.confirmDeleteDiscussion": "Delete this discussion?",
8012
+ "comments.commentThread.delete": "Delete",
8013
+ "comments.commentThread.deleteComment": "Delete comment",
8014
+ "comments.commentThread.deleteDiscussion": "Delete discussion",
7980
8015
  "comments.commentThread.leaveCommentPlaceholder": "Leave a comment…",
7981
8016
  "comments.commentThread.loadingDiscussion": "Loading discussion…",
7982
8017
  "comments.commentThread.openDiscussion": "Open discussion",
@@ -24192,128 +24227,199 @@ function Breadcrumb(handle) {
24192
24227
  };
24193
24228
  }
24194
24229
  //#endregion
24195
- //#region ../ui/src/lib/headless-button.tsx
24196
- /**
24197
- * A native button with shared interactions; callers own its content and geometry.
24198
- * @param handle - Native button props, content, and Remix mixins.
24199
- * @returns The button render function.
24200
- */
24201
- function HeadlessButton(handle) {
24202
- return () => {
24203
- let { class: className, type = "button", ...props } = handle.props;
24204
- let attributes = ariaAttributes(props);
24205
- return /* @__PURE__ */ jsx("button", {
24206
- ...attributes,
24207
- type,
24208
- class: ["bud-headless-button", className].filter(Boolean).join(" ")
24209
- });
24230
+ //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/core/vnode.js
24231
+ function createRemixElement(type, props, key) {
24232
+ return {
24233
+ $rmx: true,
24234
+ key,
24235
+ props: normalizeElementProps(props),
24236
+ type
24237
+ };
24238
+ }
24239
+ function isRemixElement(node) {
24240
+ if (typeof node !== "object" || node === null) return false;
24241
+ let type = Reflect.get(node, "type");
24242
+ let props = Reflect.get(node, "props");
24243
+ return Reflect.get(node, "$rmx") === true && (typeof type === "string" || typeof type === "function") && typeof props === "object" && props !== null;
24244
+ }
24245
+ function normalizeElementProps(props) {
24246
+ if (!props) return {};
24247
+ if (!("mix" in props)) return props;
24248
+ if (isNormalizedMix(props.mix)) return props;
24249
+ let { mix, ...rest } = props;
24250
+ let normalizedMix = normalizeMixValue(mix);
24251
+ return normalizedMix === void 0 ? rest : {
24252
+ ...rest,
24253
+ mix: normalizedMix
24210
24254
  };
24211
24255
  }
24256
+ function isNormalizedMix(mix) {
24257
+ if (!Array.isArray(mix) || mix.length === 0) return false;
24258
+ for (let i = 0; i < mix.length; i++) {
24259
+ let item = mix[i];
24260
+ if (!item || Array.isArray(item)) return false;
24261
+ }
24262
+ return true;
24263
+ }
24264
+ function normalizeMixValue(mix) {
24265
+ if (!mix) return void 0;
24266
+ let normalizedMix = [];
24267
+ flattenMixValue(mix, normalizedMix);
24268
+ return normalizedMix.length === 0 ? void 0 : normalizedMix;
24269
+ }
24270
+ function flattenMixValue(mix, out) {
24271
+ if (!mix) return;
24272
+ if (!Array.isArray(mix)) {
24273
+ out.push(mix);
24274
+ return;
24275
+ }
24276
+ for (let item of mix) flattenMixValue(item, out);
24277
+ }
24212
24278
  //#endregion
24213
- //#region ../ui/src/lib/comments/comment-data.ts
24214
- /**
24215
- * Takes the first letters of a name's first and last words.
24216
- * @param name - The author's name.
24217
- * @returns One or two capital letters, or `?` for an empty name.
24218
- */
24219
- function commentInitials(name) {
24220
- let parts = name.trim().split(/\s+/u).filter(Boolean);
24221
- return `${parts[0]?.[0] ?? "?"}${parts.length > 1 ? parts.at(-1)[0] : ""}`.toUpperCase();
24279
+ //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/jsx.js
24280
+ function jsx$1(type, props, key) {
24281
+ return createRemixElement(type, props, key);
24222
24282
  }
24283
+ //#endregion
24284
+ //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/invariant.js
24223
24285
  /**
24224
- * Gives an author a stable color.
24225
- * @param id - The author's id.
24226
- * @returns A hue from 0 to 359.
24286
+ * Use this when framework code is incorrect, indicating an internal bug rather
24287
+ * than application code error.
24288
+ *
24289
+ * @param assertion The value to check for truthiness
24290
+ * @param message Optional message to include in the error
24227
24291
  */
24228
- function commentAuthorHue(id) {
24229
- let hash = 0;
24230
- for (let character of id) hash = (hash * 31 + character.codePointAt(0)) % 360;
24231
- return hash;
24292
+ function invariant(assertion, message) {
24293
+ let prefix = "Framework invariant";
24294
+ if (assertion) return;
24295
+ throw new Error(message ? `${prefix}: ${message}` : prefix);
24232
24296
  }
24233
24297
  //#endregion
24234
- //#region ../ui/src/lib/comments/comment-thread.tsx
24298
+ //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/core/children.js
24299
+ function normalizeChildren(children) {
24300
+ for (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return children.flat(Infinity);
24301
+ return children;
24302
+ }
24303
+ function assertRemixNodes(values) {
24304
+ for (let value of values) invariant(isRemixNode(value), "Invalid child node");
24305
+ }
24306
+ function isRemixNode(value) {
24307
+ if (value == null) return true;
24308
+ let type = typeof value;
24309
+ if (type === "string" || type === "number" || type === "bigint" || type === "boolean") return true;
24310
+ if (isRemixElement(value)) return true;
24311
+ if (!Array.isArray(value)) return false;
24312
+ for (let child of value) if (!isRemixNode(child)) return false;
24313
+ return true;
24314
+ }
24315
+ //#endregion
24316
+ //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/mixins/mixin.js
24235
24317
  /**
24236
- * Shows an author's initials on a circle in their color.
24237
- * @param handle - The author, and whether the avatar is the compact size.
24238
- * @returns The avatar render function.
24318
+ * Creates a typed mixin factory that can be passed through the `mix` prop.
24319
+ *
24320
+ * @param type Mixin setup function.
24321
+ * @returns A function that captures mixin arguments and returns a descriptor.
24239
24322
  */
24240
- function CommentAvatar(handle) {
24241
- return () => {
24242
- let { author, compact = false, class: className = "" } = handle.props;
24243
- return /* @__PURE__ */ jsx(Text, {
24244
- variant: "ui-caption",
24245
- weight: "medium",
24246
- class: `flex shrink-0 items-center justify-center rounded-full text-white ${compact ? "size-5" : "size-6"} ${className}`,
24247
- title: author.name,
24248
- "data-testid": "comment-avatar",
24249
- style: { backgroundColor: `oklch(0.55 0.14 ${commentAuthorHue(author.id)})` },
24250
- children: /* @__PURE__ */ jsx("span", {
24251
- class: compact ? "scale-[0.833]" : void 0,
24252
- children: commentInitials(author.name)
24253
- })
24254
- });
24255
- };
24323
+ function createMixin(type) {
24324
+ return (...args) => ({
24325
+ type,
24326
+ args
24327
+ });
24256
24328
  }
24257
24329
  //#endregion
24258
- //#region ../ui/src/lib/dialog.tsx
24259
- var sizes = {
24260
- default: "max-w-[35rem]",
24261
- large: "max-w-[39rem]",
24262
- wide: "max-w-[51rem]"
24330
+ //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/mixins/on-mixin.js
24331
+ var onMixinType = (handle) => {
24332
+ let currentHandler = () => {};
24333
+ let currentType = "";
24334
+ let currentCapture = false;
24335
+ let currentNode = null;
24336
+ let reentry = null;
24337
+ let stableHandler = (event) => {
24338
+ reentry?.abort(new DOMException("", "EventReentry"));
24339
+ reentry = new AbortController();
24340
+ currentHandler(event, reentry.signal);
24341
+ };
24342
+ handle.addEventListener("insert", (event) => {
24343
+ currentNode = event.node;
24344
+ currentNode.addEventListener(currentType, stableHandler, currentCapture);
24345
+ });
24346
+ handle.addEventListener("remove", () => {
24347
+ currentNode?.removeEventListener(currentType, stableHandler, currentCapture);
24348
+ currentNode = null;
24349
+ reentry?.abort(new DOMException("", "AbortError"));
24350
+ });
24351
+ return (type, handler, captureBoolean = false) => {
24352
+ let previousType = currentType;
24353
+ let previousCapture = currentCapture;
24354
+ let needsRebind = currentType !== type || currentCapture !== captureBoolean;
24355
+ currentType = type;
24356
+ currentHandler = handler;
24357
+ currentCapture = captureBoolean;
24358
+ if (needsRebind && currentNode) {
24359
+ currentNode.removeEventListener(previousType, stableHandler, previousCapture);
24360
+ currentNode.addEventListener(type, stableHandler, captureBoolean);
24361
+ }
24362
+ return handle.element;
24363
+ };
24263
24364
  };
24365
+ var onMixin = createMixin(onMixinType);
24264
24366
  /**
24265
- * The one modal: a native dialog on a dimmed, blurred backdrop whose content is a FieldCard,
24266
- * raised above the page. It takes focus when it opens, returns it when it closes, and closes on
24267
- * Escape or a click on the backdrop.
24268
- * @param handle - The label, size, close handler and content.
24269
- * @returns The dialog's render function.
24367
+ * Attaches a typed DOM event handler through the mixin system.
24368
+ *
24369
+ * @param type Event type to listen for.
24370
+ * @param handler Event handler.
24371
+ * @param captureBoolean Whether to listen during capture.
24372
+ * @returns A mixin descriptor for the target element.
24270
24373
  */
24271
- function Dialog(handle) {
24272
- let pressedOutside = false;
24273
- function close() {
24274
- if (handle.props.dismissible !== false) handle.props.onClose();
24275
- }
24276
- return () => {
24277
- let { label, size = "default", initialFocus, children } = handle.props;
24278
- return /* @__PURE__ */ jsx("dialog", {
24279
- "aria-label": label,
24280
- class: `fixed inset-0 m-auto flex max-h-full w-full flex-col overflow-hidden border-0 bg-transparent p-3 text-ink transition-[opacity,scale] duration-200 ease-out backdrop:bg-black/25 backdrop:backdrop-blur-sm backdrop:transition-opacity backdrop:duration-200 starting:scale-[0.97] starting:opacity-0 starting:backdrop:opacity-0 motion-reduce:transition-none motion-reduce:backdrop:transition-none sm:p-6 ${sizes[size]}`,
24281
- mix: [
24282
- ref((node, signal) => openDialog(node, signal, initialFocus)),
24283
- on("cancel", (event) => {
24284
- event.preventDefault();
24285
- close();
24286
- }),
24287
- on("pointerdown", (event) => {
24288
- pressedOutside = event.target === event.currentTarget;
24289
- }),
24290
- on("click", (event) => {
24291
- if (pressedOutside && event.target === event.currentTarget) close();
24292
- })
24293
- ],
24294
- children
24295
- });
24296
- };
24374
+ function on$1(type, handler, captureBoolean) {
24375
+ return onMixin(type, handler, captureBoolean);
24297
24376
  }
24377
+ //#endregion
24378
+ //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/create-element.js
24298
24379
  /**
24299
- * Owns a native modal and returns focus once Remix removes its host node.
24300
- * @param node - The mounted dialog.
24301
- * @param signal - The host's lifetime, including its removal.
24302
- * @param initialFocus - An optional selector for the element to focus first.
24380
+ * Creates a Remix virtual element from a JSX-like call signature.
24381
+ *
24382
+ * @param type Host tag, component function, or mixin host placeholder.
24383
+ * @param props Element props.
24384
+ * @param children Child nodes.
24385
+ * @returns A Remix virtual element.
24303
24386
  */
24304
- function openDialog(node, signal, initialFocus) {
24305
- let document = node.ownerDocument;
24306
- let previous = document.activeElement;
24307
- node.showModal();
24308
- if (initialFocus) queueMicrotask(() => {
24309
- if (!signal.aborted) node.querySelector(initialFocus)?.focus();
24310
- });
24311
- signal.addEventListener("abort", () => {
24312
- node.close();
24313
- queueMicrotask(() => {
24314
- if (previous instanceof HTMLElement && previous.isConnected && [...document.querySelectorAll("dialog:modal")].every((dialog) => dialog.contains(previous))) previous.focus({ preventScroll: true });
24315
- });
24316
- }, { once: true });
24387
+ function createElement$1(type, props, ...children) {
24388
+ let nextProps = { ...props ?? {} };
24389
+ if (isMixinElementType(type)) {
24390
+ if (children.length > 0) console.error(/* @__PURE__ */ new Error("mixin elements must not receive children"));
24391
+ } else {
24392
+ assertRemixNodes(children);
24393
+ nextProps.children = normalizeChildren(children);
24394
+ }
24395
+ if (nextProps.key != null) {
24396
+ let { key, ...rest } = nextProps;
24397
+ return jsx$1(type, rest, key);
24398
+ }
24399
+ return jsx$1(type, nextProps);
24400
+ }
24401
+ function isMixinElementType(type) {
24402
+ return typeof type === "function" && "__rmxMixinElementType" in type;
24403
+ }
24404
+ //#endregion
24405
+ //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/mixins/attrs-mixin.js
24406
+ var attrsMixin = createMixin((handle) => (defaults, props) => {
24407
+ let nextProps = props;
24408
+ for (let key in defaults) {
24409
+ if (props[key] !== void 0) continue;
24410
+ if (nextProps === props) nextProps = { ...props };
24411
+ nextProps[key] = defaults[key];
24412
+ }
24413
+ return nextProps === props ? handle.element : createElement$1(handle.element, nextProps);
24414
+ });
24415
+ /**
24416
+ * Applies default host props unless the element already provides them explicitly.
24417
+ *
24418
+ * @param defaults Default props to apply when the element does not already define them.
24419
+ * @returns A mixin descriptor that provides default host props.
24420
+ */
24421
+ function attrs(defaults) {
24422
+ return attrsMixin(defaults);
24317
24423
  }
24318
24424
  //#endregion
24319
24425
  //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/anchor.js
@@ -24760,548 +24866,238 @@ function anchor$1(floating, anchorTarget, options = {}) {
24760
24866
  };
24761
24867
  }
24762
24868
  //#endregion
24763
- //#region ../ui/src/lib/shortcut.ts
24764
- var symbols = {
24765
- arrowup: "↑",
24766
- arrowdown: "↓",
24767
- arrowleft: "←",
24768
- arrowright: "→",
24769
- enter: "↵",
24770
- escape: "Esc",
24771
- backspace: "⌫",
24772
- tab: "⇥",
24773
- space: "Space"
24774
- };
24775
- /**
24776
- * Reads a combo such as `mod+shift+k`; the last part is the key, so `mod++` means mod and plus.
24777
- * @param combo - Modifiers and a key joined by `+`.
24778
- * @returns The key and its modifiers.
24779
- */
24780
- function parseShortcut(combo) {
24781
- let separator = combo.length < 2 ? -1 : combo.lastIndexOf("+", combo.length - 2);
24782
- let modifiers = separator < 0 ? [] : combo.slice(0, separator).toLowerCase().split("+");
24783
- return {
24784
- key: combo.slice(separator + 1),
24785
- mod: modifiers.includes("mod"),
24786
- shift: modifiers.includes("shift"),
24787
- alt: modifiers.includes("alt")
24869
+ //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/lib/outside-click.js
24870
+ var onOutsideClick = createMixin((handle) => {
24871
+ let active = false;
24872
+ let handler = () => {};
24873
+ let isInsideTarget = () => false;
24874
+ let stopPropagation = true;
24875
+ handle.addEventListener("insert", (event) => {
24876
+ let node = event.node;
24877
+ node.ownerDocument.addEventListener("click", (event) => {
24878
+ let target = event.target instanceof Node ? event.target : null;
24879
+ if (!active || target && (node.contains(target) || isInsideTarget(target))) return;
24880
+ if (stopPropagation) event.stopPropagation();
24881
+ handler(target);
24882
+ }, {
24883
+ capture: true,
24884
+ signal: handle.signal
24885
+ });
24886
+ });
24887
+ return (nextActive, nextHandler, nextIsInsideTarget = () => false, nextStopPropagation = true) => {
24888
+ active = nextActive;
24889
+ handler = nextHandler;
24890
+ isInsideTarget = nextIsInsideTarget;
24891
+ stopPropagation = nextStopPropagation;
24788
24892
  };
24789
- }
24790
- /**
24791
- * The keycaps to show for a combo, in the platform's order and notation.
24792
- * @param combo - Modifiers and a key joined by `+`.
24793
- * @param apple - Whether to use Apple symbols; detected from the browser by default.
24794
- * @returns One label per key.
24795
- */
24796
- function shortcutKeys(combo, apple = isApple()) {
24797
- let { key, mod, shift, alt } = parseShortcut(combo);
24798
- let label = symbols[key.toLowerCase()] ?? (key.length === 1 ? key.toUpperCase() : key);
24799
- if (apple) return [
24800
- alt && "⌥",
24801
- shift && "⇧",
24802
- mod && "⌘",
24803
- label
24804
- ].filter((part) => part !== false);
24805
- return [
24806
- mod && "Ctrl",
24807
- alt && "Alt",
24808
- shift && "Shift",
24809
- label
24810
- ].filter((part) => part !== false);
24811
- }
24812
- function isApple() {
24813
- return typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.platform);
24814
- }
24893
+ });
24815
24894
  //#endregion
24816
- //#region ../ui/src/lib/tooltip.tsx
24817
- var delay = 400;
24818
- var warmFor = 300;
24819
- var closedAt = 0;
24820
- /**
24821
- * Names a control on hover or keyboard focus, with its shortcut. The control keeps its own
24822
- * accessible name; the tooltip describes it.
24823
- * @param handle - The label, optional shortcut and the control it describes.
24824
- * @returns The tooltip render function.
24825
- */
24826
- function Tooltip(handle) {
24827
- let open = false;
24828
- let timer;
24829
- let trigger;
24830
- let release = () => {};
24831
- let id = `${handle.id}-tooltip`;
24832
- function show(immediately) {
24833
- clearTimeout(timer);
24834
- if (open) return;
24835
- let reveal = () => {
24836
- open = true;
24837
- handle.update();
24895
+ //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/lib/scroll-lock.js
24896
+ var scrollLocks = /* @__PURE__ */ new WeakMap();
24897
+ function lockScroll(targetDocument = globalThis.document) {
24898
+ if (!targetDocument?.body || !targetDocument.defaultView) return () => {};
24899
+ let document = targetDocument;
24900
+ let documentElement = document.documentElement;
24901
+ let view = document.defaultView;
24902
+ let state = scrollLocks.get(document);
24903
+ if (!state) {
24904
+ let scrollX = view.scrollX;
24905
+ let scrollY = view.scrollY;
24906
+ let scrollbarWidth = documentElement.clientWidth > 0 ? Math.max(view.innerWidth - documentElement.clientWidth, 0) : 0;
24907
+ let computedScrollbarGutter = view.getComputedStyle(documentElement).scrollbarGutter;
24908
+ state = {
24909
+ count: 0,
24910
+ documentOverflow: documentElement.style.overflow,
24911
+ documentScrollbarGutter: documentElement.style.scrollbarGutter,
24912
+ scrollX,
24913
+ scrollY
24838
24914
  };
24839
- if (immediately || Date.now() - closedAt < warmFor) reveal();
24840
- else timer = setTimeout(reveal, delay);
24841
- }
24842
- function hide() {
24843
- clearTimeout(timer);
24844
- if (!open) return;
24845
- open = false;
24846
- closedAt = Date.now();
24847
- handle.update();
24915
+ documentElement.style.overflow = "hidden";
24916
+ if (scrollbarWidth > 0 && computedScrollbarGutter === "auto") documentElement.style.scrollbarGutter = "stable";
24917
+ scrollLocks.set(document, state);
24848
24918
  }
24849
- return () => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
24850
- class: "inline-flex",
24851
- mix: [
24852
- ref((node, signal) => {
24853
- trigger = node.firstElementChild instanceof HTMLElement ? node.firstElementChild : node;
24854
- trigger.setAttribute("aria-describedby", id);
24855
- let escape = (event) => {
24856
- if (event.key === "Escape") hide();
24857
- };
24858
- document.addEventListener("keydown", escape, { signal });
24859
- signal.addEventListener("abort", () => clearTimeout(timer), { once: true });
24860
- }),
24861
- on("pointerenter", (event) => {
24862
- if (event.pointerType === "mouse") show(false);
24863
- }),
24864
- on("pointerleave", hide),
24865
- on("pointerdown", hide),
24866
- on("focusin", (event) => {
24867
- if (event.target.dataset.focusMethod === "keyboard") show(true);
24868
- }),
24869
- on("focusout", hide)
24870
- ],
24871
- children: handle.props.children
24872
- }), /* @__PURE__ */ jsxs("div", {
24873
- id,
24874
- role: "tooltip",
24875
- popover: "manual",
24876
- class: "bud-tooltip pointer-events-none m-0 inline-flex items-center gap-1.5 rounded-[10px] px-2 py-1.5 transition-[opacity,scale] duration-100 ease-out starting:scale-95 starting:opacity-0 motion-reduce:transition-none",
24877
- mix: ref((node, signal) => {
24878
- if (!open || !trigger) return;
24879
- node.showPopover();
24880
- release = anchor$1(node, trigger, {
24881
- placement: handle.props.placement ?? "top",
24882
- offset: 6
24883
- });
24884
- signal.addEventListener("abort", () => {
24885
- release();
24886
- if (node.isConnected && node.matches(":popover-open")) node.hidePopover();
24887
- }, { once: true });
24888
- }),
24889
- hidden: !open,
24890
- children: [/* @__PURE__ */ jsx(Text, {
24891
- variant: "ui-caption",
24892
- weight: "medium",
24893
- children: handle.props.label
24894
- }), handle.props.shortcut ? /* @__PURE__ */ jsx(Kbd, { shortcut: handle.props.shortcut }) : null]
24895
- }, open ? "open" : "closed")] });
24919
+ state.count++;
24920
+ let unlocked = false;
24921
+ return () => {
24922
+ if (unlocked) return;
24923
+ unlocked = true;
24924
+ let currentState = scrollLocks.get(document);
24925
+ if (!currentState) return;
24926
+ currentState.count--;
24927
+ if (currentState.count > 0) return;
24928
+ scrollLocks.delete(document);
24929
+ documentElement.style.overflow = currentState.documentOverflow;
24930
+ documentElement.style.scrollbarGutter = currentState.documentScrollbarGutter;
24931
+ view.scrollTo(currentState.scrollX, currentState.scrollY);
24932
+ };
24896
24933
  }
24897
- /**
24898
- * A shortcut as keycaps, such as ⌘ K, in the platform's notation.
24899
- * @param handle - A combo such as `mod+shift+k`.
24900
- * @returns The keycaps render function.
24901
- */
24902
- function Kbd(handle) {
24903
- return () => /* @__PURE__ */ jsx("span", {
24904
- class: `inline-flex items-center gap-0.5 ${handle.props.class ?? ""}`,
24905
- children: shortcutKeys(handle.props.shortcut).map((key, index) => /* @__PURE__ */ jsx(Text, {
24906
- as: "kbd",
24907
- variant: "kbd",
24908
- class: "inline-flex h-4.5 min-w-4.5 items-center justify-center rounded-[5px] bg-current/12 px-1 opacity-80",
24909
- "data-slot": "kbd",
24910
- children: key
24911
- }, index))
24934
+ //#endregion
24935
+ //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/popover.js
24936
+ function PopoverProvider(handle) {
24937
+ handle.context.set({
24938
+ hideFocusTarget: null,
24939
+ showFocusTarget: null,
24940
+ surface: null,
24941
+ anchor: null
24912
24942
  });
24943
+ return () => handle.props.children;
24913
24944
  }
24914
- //#endregion
24915
- //#region ../ui/src/lib/icon-button.tsx
24916
- /**
24917
- * Compact control shared by the canvas, sidebar, and embedded product demo.
24918
- * @param handle - Native button props, icon, label, and active state.
24919
- * @returns The button render function.
24920
- */
24921
- function IconButton(handle) {
24922
- return () => {
24923
- let { icon, label, active, surface, size = "default", iconSize = size === "small" ? 14 : 16, tooltip, shortcut, class: className = "", ...props } = handle.props;
24924
- let button = /* @__PURE__ */ jsx(HeadlessButton, {
24925
- ...props,
24926
- "aria-label": label,
24927
- title: tooltip ? void 0 : props.title ?? label,
24928
- "aria-pressed": props["aria-pressed"] ?? active,
24929
- "data-active": active,
24930
- class: `bud-interaction ${surface ? "bud-control" : ""} flex shrink-0 items-center justify-center ${size === "small" ? "size-6 rounded-md" : "size-control rounded-lg"} ${className}`,
24931
- children: /* @__PURE__ */ jsx(Icon, {
24932
- name: icon,
24933
- size: iconSize
24934
- })
24945
+ var anchorMixin = createMixin((handle) => {
24946
+ let context = handle.context.get(PopoverProvider);
24947
+ return (options) => {
24948
+ handle.queueTask((node) => {
24949
+ context.anchor = {
24950
+ target: node,
24951
+ options
24952
+ };
24935
24953
  });
24936
- return tooltip ? /* @__PURE__ */ jsx(Tooltip, {
24937
- label,
24938
- shortcut,
24939
- children: button
24940
- }) : button;
24941
24954
  };
24955
+ });
24956
+ function anchorContains(anchor, target) {
24957
+ return anchor?.target instanceof HTMLElement && anchor.target.contains(target);
24942
24958
  }
24943
- //#endregion
24944
- //#region ../ui/src/lib/scroll-fade.ts
24945
- /**
24946
- * Fades a scroller's edges while it has more content past them, after Divine's scroll-fade
24947
- * utility. The fade only applies while the content overflows, so a short list keeps crisp edges.
24948
- * @param axis - The direction the element scrolls.
24949
- * @returns A mixin for the scrolling element.
24950
- */
24951
- function scrollFade(axis = "x") {
24952
- return ref((node, signal) => {
24953
- let update = () => {
24954
- if (axis === "x" ? node.scrollWidth > node.clientWidth + 1 : node.scrollHeight > node.clientHeight + 1) node.dataset.scrollFade = axis;
24955
- else delete node.dataset.scrollFade;
24956
- };
24957
- let resize = new ResizeObserver(update);
24958
- let mutation = new MutationObserver(update);
24959
- resize.observe(node);
24960
- mutation.observe(node, {
24961
- childList: true,
24962
- subtree: true,
24963
- characterData: true
24964
- });
24965
- update();
24959
+ var surfaceMixin = createMixin((handle) => {
24960
+ let openProp = false;
24961
+ let cleanupAnchor = () => {};
24962
+ let unlockScroll = () => {};
24963
+ let context = handle.context.get(PopoverProvider);
24964
+ handle.queueTask((node, signal) => {
24965
+ context.surface = node;
24966
24966
  signal.addEventListener("abort", () => {
24967
- resize.disconnect();
24968
- mutation.disconnect();
24969
- }, { once: true });
24967
+ if (context.surface === node) context.surface = null;
24968
+ });
24970
24969
  });
24971
- }
24972
- //#endregion
24973
- //#region ../ui/src/lib/workspace.tsx
24974
- /**
24975
- * A panel's toolbar: content on the left, such as a path, and actions on the right. On narrow
24976
- * screens the actions scroll, fading at their edges.
24977
- * @param handle - The start content, the actions, and whether a line separates it from the
24978
- * panel, which content that fades as it scrolls doesn't need.
24979
- * @returns The toolbar render function.
24980
- */
24981
- function WorkspaceToolbar(handle) {
24982
- return () => /* @__PURE__ */ jsxs("div", {
24983
- class: `bud-toolbar flex h-12 shrink-0 items-center gap-2 ps-2 pe-2 ${handle.props.divider === false ? "" : "border-b border-line"}`,
24984
- children: [handle.props.start, /* @__PURE__ */ jsx(ToolbarGroup, {
24985
- class: "ms-auto min-w-0 [scrollbar-width:none] max-md:overflow-x-auto max-md:p-1",
24986
- mix: scrollFade("x"),
24987
- children: handle.props.children
24988
- })]
24970
+ return (options) => {
24971
+ let wasOpen = openProp;
24972
+ openProp = options.open;
24973
+ handle.queueTask(async (node) => {
24974
+ if (openProp && !wasOpen) node.showPopover();
24975
+ else if (!openProp && wasOpen) node.hidePopover();
24976
+ });
24977
+ return [
24978
+ attrs({ popover: "manual" }),
24979
+ on$1("beforetoggle", (event) => {
24980
+ if (event.newState === "open") {
24981
+ let anchor = context.anchor;
24982
+ if (!anchor) throw new Error("popover.surface() requires a registered anchor before opening");
24983
+ cleanupAnchor = anchor$1(event.currentTarget, anchor.target, anchor.options);
24984
+ unlockScroll = lockScroll();
24985
+ } else if (event.newState === "closed") {
24986
+ cleanupAnchor();
24987
+ unlockScroll();
24988
+ }
24989
+ }),
24990
+ on$1("toggle", async (event) => {
24991
+ if (event.newState === "open") context.showFocusTarget?.focus();
24992
+ else if (event.newState === "closed" && options.restoreFocusOnHide !== false) context.hideFocusTarget?.focus();
24993
+ }),
24994
+ on$1("keydown", (event) => {
24995
+ if (event.key === "Escape") {
24996
+ event.preventDefault();
24997
+ options.onHide({ reason: "escape-key" });
24998
+ }
24999
+ }),
25000
+ onOutsideClick(openProp, (target) => {
25001
+ options.onHide({
25002
+ reason: "outside-click",
25003
+ target
25004
+ });
25005
+ }, (target) => options.closeOnAnchorClick === false && anchorContains(context.anchor, target), options.stopOutsideClickPropagation ?? true)
25006
+ ];
25007
+ };
25008
+ });
25009
+ var focusOnShowMixin = createMixin((handle) => {
25010
+ handle.addEventListener("insert", (event) => {
25011
+ let context = handle.context.get(PopoverProvider);
25012
+ context.showFocusTarget = event.node;
25013
+ });
25014
+ });
25015
+ var focusOnHideMixin = createMixin((handle) => {
25016
+ handle.addEventListener("insert", (event) => {
25017
+ let context = handle.context.get(PopoverProvider);
25018
+ context.hideFocusTarget = event.node;
24989
25019
  });
25020
+ });
25021
+ var Context$1 = PopoverProvider;
25022
+ var anchor = anchorMixin;
25023
+ var surface = surfaceMixin;
25024
+ var focusOnHide = focusOnHideMixin;
25025
+ var focusOnShow = focusOnShowMixin;
25026
+ //#endregion
25027
+ //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/lib/typeahead.js
25028
+ var HIDDEN_TYPEAHEAD_TIMEOUT = 750;
25029
+ var hiddenTypeahead = createMixin((handle) => {
25030
+ let text = "";
25031
+ let timeoutId = 0;
25032
+ function clearTypeahead() {
25033
+ clearTimeout(timeoutId);
25034
+ timeoutId = 0;
25035
+ text = "";
25036
+ }
25037
+ function updateTypeahead(nextText, onTypeahead) {
25038
+ text = nextText;
25039
+ onTypeahead(text);
25040
+ }
25041
+ handle.addEventListener("remove", clearTypeahead);
25042
+ return (onTypeahead) => [on$1("focusout", (event) => {
25043
+ if (event.relatedTarget instanceof Node && event.currentTarget.contains(event.relatedTarget)) return;
25044
+ clearTypeahead();
25045
+ }), on$1("keydown", (event) => {
25046
+ if (event.key.length === 1 && !event.ctrlKey && !event.altKey && !event.metaKey) {
25047
+ clearTimeout(timeoutId);
25048
+ updateTypeahead(text + event.key.toLowerCase(), onTypeahead);
25049
+ timeoutId = window.setTimeout(clearTypeahead, HIDDEN_TYPEAHEAD_TIMEOUT);
25050
+ } else if (event.key === "Escape") clearTypeahead();
25051
+ else if (event.key === "Backspace" && text.length > 0) {
25052
+ clearTimeout(timeoutId);
25053
+ updateTypeahead(text.slice(0, -1), onTypeahead);
25054
+ timeoutId = window.setTimeout(clearTypeahead, HIDDEN_TYPEAHEAD_TIMEOUT);
25055
+ }
25056
+ })];
25057
+ });
25058
+ function itemMatchesSearchText(item, text, getSearchValues) {
25059
+ let values = getSearchValues(item);
25060
+ let searchValues = Array.isArray(values) ? values : [values];
25061
+ let normalizedText = text.toLowerCase();
25062
+ return searchValues.some((value) => value.toLowerCase().startsWith(normalizedText));
25063
+ }
25064
+ function matchNextItemBySearchText(text, items, options) {
25065
+ if (text === "") return null;
25066
+ for (let offset = 1; offset <= items.length; offset++) {
25067
+ let item = items[(options.fromIndex + offset + items.length) % items.length];
25068
+ if (itemMatchesSearchText(item, text, options.getSearchValues)) return item;
25069
+ }
25070
+ return null;
24990
25071
  }
25072
+ //#endregion
25073
+ //#region ../ui/src/lib/headless-button.tsx
24991
25074
  /**
24992
- * A row of toolbar buttons and separators, spaced alike in every toolbar.
24993
- * @param handle - Native div props and the buttons.
24994
- * @returns The group render function.
25075
+ * A native button with shared interactions; callers own its content and geometry.
25076
+ * @param handle - Native button props, content, and Remix mixins.
25077
+ * @returns The button render function.
24995
25078
  */
24996
- function ToolbarGroup(handle) {
25079
+ function HeadlessButton(handle) {
24997
25080
  return () => {
24998
- let { class: className = "", children, ...props } = handle.props;
24999
- return /* @__PURE__ */ jsx("div", {
25000
- ...props,
25001
- class: `flex items-center gap-1.5 ${className}`,
25002
- children
25081
+ let { class: className, type = "button", ...props } = handle.props;
25082
+ let attributes = ariaAttributes(props);
25083
+ return /* @__PURE__ */ jsx("button", {
25084
+ ...attributes,
25085
+ type,
25086
+ class: ["bud-headless-button", className].filter(Boolean).join(" ")
25003
25087
  });
25004
25088
  };
25005
25089
  }
25006
25090
  //#endregion
25007
- //#region ../ui/src/lib/toast-store.ts
25008
- var defaultDuration = 4e3;
25009
- var items = [];
25010
- var listeners = /* @__PURE__ */ new Set();
25011
- var next = 0;
25012
- function publish() {
25013
- for (let listener of listeners) listener(items);
25091
+ //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/lib/flash-attribute.js
25092
+ function wait(ms) {
25093
+ return new Promise((resolve) => {
25094
+ setTimeout(resolve, ms);
25095
+ });
25014
25096
  }
25015
- /**
25016
- * Shows a notification; the newest is in front.
25017
- * @param title - The message.
25018
- * @param options - A description, type, duration or stable id.
25019
- * @returns The toast's id, for dismissing it early.
25020
- */
25021
- function toast(title, options = {}) {
25022
- let id = options.id ?? `toast-${++next}`;
25023
- items = [{
25024
- id,
25025
- title,
25026
- description: options.description,
25027
- type: options.type ?? "default",
25028
- duration: options.duration ?? defaultDuration,
25029
- removed: false
25030
- }, ...items.filter((existing) => existing.id !== id)];
25031
- publish();
25032
- return id;
25033
- }
25034
- toast.success = (title, options = {}) => toast(title, {
25035
- ...options,
25036
- type: "success"
25037
- });
25038
- toast.error = (title, options = {}) => toast(title, {
25039
- ...options,
25040
- type: "error"
25041
- });
25042
- /**
25043
- * Starts a toast's exit; the Toaster removes it once it has animated out.
25044
- * @param id - The toast to dismiss; every toast when omitted.
25045
- */
25046
- toast.dismiss = (id) => {
25047
- items = items.map((item) => id === void 0 || item.id === id ? {
25048
- ...item,
25049
- removed: true
25050
- } : item);
25051
- publish();
25052
- };
25053
- //#endregion
25054
- //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/core/vnode.js
25055
- function createRemixElement(type, props, key) {
25056
- return {
25057
- $rmx: true,
25058
- key,
25059
- props: normalizeElementProps(props),
25060
- type
25061
- };
25062
- }
25063
- function isRemixElement(node) {
25064
- if (typeof node !== "object" || node === null) return false;
25065
- let type = Reflect.get(node, "type");
25066
- let props = Reflect.get(node, "props");
25067
- return Reflect.get(node, "$rmx") === true && (typeof type === "string" || typeof type === "function") && typeof props === "object" && props !== null;
25068
- }
25069
- function normalizeElementProps(props) {
25070
- if (!props) return {};
25071
- if (!("mix" in props)) return props;
25072
- if (isNormalizedMix(props.mix)) return props;
25073
- let { mix, ...rest } = props;
25074
- let normalizedMix = normalizeMixValue(mix);
25075
- return normalizedMix === void 0 ? rest : {
25076
- ...rest,
25077
- mix: normalizedMix
25078
- };
25079
- }
25080
- function isNormalizedMix(mix) {
25081
- if (!Array.isArray(mix) || mix.length === 0) return false;
25082
- for (let i = 0; i < mix.length; i++) {
25083
- let item = mix[i];
25084
- if (!item || Array.isArray(item)) return false;
25085
- }
25086
- return true;
25087
- }
25088
- function normalizeMixValue(mix) {
25089
- if (!mix) return void 0;
25090
- let normalizedMix = [];
25091
- flattenMixValue(mix, normalizedMix);
25092
- return normalizedMix.length === 0 ? void 0 : normalizedMix;
25093
- }
25094
- function flattenMixValue(mix, out) {
25095
- if (!mix) return;
25096
- if (!Array.isArray(mix)) {
25097
- out.push(mix);
25098
- return;
25099
- }
25100
- for (let item of mix) flattenMixValue(item, out);
25101
- }
25102
- //#endregion
25103
- //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/jsx.js
25104
- function jsx$1(type, props, key) {
25105
- return createRemixElement(type, props, key);
25106
- }
25107
- //#endregion
25108
- //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/invariant.js
25109
- /**
25110
- * Use this when framework code is incorrect, indicating an internal bug rather
25111
- * than application code error.
25112
- *
25113
- * @param assertion The value to check for truthiness
25114
- * @param message Optional message to include in the error
25115
- */
25116
- function invariant(assertion, message) {
25117
- let prefix = "Framework invariant";
25118
- if (assertion) return;
25119
- throw new Error(message ? `${prefix}: ${message}` : prefix);
25120
- }
25121
- //#endregion
25122
- //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/core/children.js
25123
- function normalizeChildren(children) {
25124
- for (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return children.flat(Infinity);
25125
- return children;
25126
- }
25127
- function assertRemixNodes(values) {
25128
- for (let value of values) invariant(isRemixNode(value), "Invalid child node");
25129
- }
25130
- function isRemixNode(value) {
25131
- if (value == null) return true;
25132
- let type = typeof value;
25133
- if (type === "string" || type === "number" || type === "bigint" || type === "boolean") return true;
25134
- if (isRemixElement(value)) return true;
25135
- if (!Array.isArray(value)) return false;
25136
- for (let child of value) if (!isRemixNode(child)) return false;
25137
- return true;
25138
- }
25139
- //#endregion
25140
- //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/mixins/mixin.js
25141
- /**
25142
- * Creates a typed mixin factory that can be passed through the `mix` prop.
25143
- *
25144
- * @param type Mixin setup function.
25145
- * @returns A function that captures mixin arguments and returns a descriptor.
25146
- */
25147
- function createMixin(type) {
25148
- return (...args) => ({
25149
- type,
25150
- args
25151
- });
25152
- }
25153
- //#endregion
25154
- //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/mixins/on-mixin.js
25155
- var onMixinType = (handle) => {
25156
- let currentHandler = () => {};
25157
- let currentType = "";
25158
- let currentCapture = false;
25159
- let currentNode = null;
25160
- let reentry = null;
25161
- let stableHandler = (event) => {
25162
- reentry?.abort(new DOMException("", "EventReentry"));
25163
- reentry = new AbortController();
25164
- currentHandler(event, reentry.signal);
25165
- };
25166
- handle.addEventListener("insert", (event) => {
25167
- currentNode = event.node;
25168
- currentNode.addEventListener(currentType, stableHandler, currentCapture);
25169
- });
25170
- handle.addEventListener("remove", () => {
25171
- currentNode?.removeEventListener(currentType, stableHandler, currentCapture);
25172
- currentNode = null;
25173
- reentry?.abort(new DOMException("", "AbortError"));
25174
- });
25175
- return (type, handler, captureBoolean = false) => {
25176
- let previousType = currentType;
25177
- let previousCapture = currentCapture;
25178
- let needsRebind = currentType !== type || currentCapture !== captureBoolean;
25179
- currentType = type;
25180
- currentHandler = handler;
25181
- currentCapture = captureBoolean;
25182
- if (needsRebind && currentNode) {
25183
- currentNode.removeEventListener(previousType, stableHandler, previousCapture);
25184
- currentNode.addEventListener(type, stableHandler, captureBoolean);
25185
- }
25186
- return handle.element;
25187
- };
25188
- };
25189
- var onMixin = createMixin(onMixinType);
25190
- /**
25191
- * Attaches a typed DOM event handler through the mixin system.
25192
- *
25193
- * @param type Event type to listen for.
25194
- * @param handler Event handler.
25195
- * @param captureBoolean Whether to listen during capture.
25196
- * @returns A mixin descriptor for the target element.
25197
- */
25198
- function on$1(type, handler, captureBoolean) {
25199
- return onMixin(type, handler, captureBoolean);
25200
- }
25201
- //#endregion
25202
- //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/create-element.js
25203
- /**
25204
- * Creates a Remix virtual element from a JSX-like call signature.
25205
- *
25206
- * @param type Host tag, component function, or mixin host placeholder.
25207
- * @param props Element props.
25208
- * @param children Child nodes.
25209
- * @returns A Remix virtual element.
25210
- */
25211
- function createElement$1(type, props, ...children) {
25212
- let nextProps = { ...props ?? {} };
25213
- if (isMixinElementType(type)) {
25214
- if (children.length > 0) console.error(/* @__PURE__ */ new Error("mixin elements must not receive children"));
25215
- } else {
25216
- assertRemixNodes(children);
25217
- nextProps.children = normalizeChildren(children);
25218
- }
25219
- if (nextProps.key != null) {
25220
- let { key, ...rest } = nextProps;
25221
- return jsx$1(type, rest, key);
25222
- }
25223
- return jsx$1(type, nextProps);
25224
- }
25225
- function isMixinElementType(type) {
25226
- return typeof type === "function" && "__rmxMixinElementType" in type;
25227
- }
25228
- //#endregion
25229
- //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/mixins/attrs-mixin.js
25230
- var attrsMixin = createMixin((handle) => (defaults, props) => {
25231
- let nextProps = props;
25232
- for (let key in defaults) {
25233
- if (props[key] !== void 0) continue;
25234
- if (nextProps === props) nextProps = { ...props };
25235
- nextProps[key] = defaults[key];
25236
- }
25237
- return nextProps === props ? handle.element : createElement$1(handle.element, nextProps);
25238
- });
25239
- /**
25240
- * Applies default host props unless the element already provides them explicitly.
25241
- *
25242
- * @param defaults Default props to apply when the element does not already define them.
25243
- * @returns A mixin descriptor that provides default host props.
25244
- */
25245
- function attrs(defaults) {
25246
- return attrsMixin(defaults);
25247
- }
25248
- //#endregion
25249
- //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/lib/typeahead.js
25250
- var HIDDEN_TYPEAHEAD_TIMEOUT = 750;
25251
- var hiddenTypeahead = createMixin((handle) => {
25252
- let text = "";
25253
- let timeoutId = 0;
25254
- function clearTypeahead() {
25255
- clearTimeout(timeoutId);
25256
- timeoutId = 0;
25257
- text = "";
25258
- }
25259
- function updateTypeahead(nextText, onTypeahead) {
25260
- text = nextText;
25261
- onTypeahead(text);
25262
- }
25263
- handle.addEventListener("remove", clearTypeahead);
25264
- return (onTypeahead) => [on$1("focusout", (event) => {
25265
- if (event.relatedTarget instanceof Node && event.currentTarget.contains(event.relatedTarget)) return;
25266
- clearTypeahead();
25267
- }), on$1("keydown", (event) => {
25268
- if (event.key.length === 1 && !event.ctrlKey && !event.altKey && !event.metaKey) {
25269
- clearTimeout(timeoutId);
25270
- updateTypeahead(text + event.key.toLowerCase(), onTypeahead);
25271
- timeoutId = window.setTimeout(clearTypeahead, HIDDEN_TYPEAHEAD_TIMEOUT);
25272
- } else if (event.key === "Escape") clearTypeahead();
25273
- else if (event.key === "Backspace" && text.length > 0) {
25274
- clearTimeout(timeoutId);
25275
- updateTypeahead(text.slice(0, -1), onTypeahead);
25276
- timeoutId = window.setTimeout(clearTypeahead, HIDDEN_TYPEAHEAD_TIMEOUT);
25277
- }
25278
- })];
25279
- });
25280
- function itemMatchesSearchText(item, text, getSearchValues) {
25281
- let values = getSearchValues(item);
25282
- let searchValues = Array.isArray(values) ? values : [values];
25283
- let normalizedText = text.toLowerCase();
25284
- return searchValues.some((value) => value.toLowerCase().startsWith(normalizedText));
25285
- }
25286
- function matchNextItemBySearchText(text, items, options) {
25287
- if (text === "") return null;
25288
- for (let offset = 1; offset <= items.length; offset++) {
25289
- let item = items[(options.fromIndex + offset + items.length) % items.length];
25290
- if (itemMatchesSearchText(item, text, options.getSearchValues)) return item;
25291
- }
25292
- return null;
25293
- }
25294
- //#endregion
25295
- //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/lib/flash-attribute.js
25296
- function wait(ms) {
25297
- return new Promise((resolve) => {
25298
- setTimeout(resolve, ms);
25299
- });
25300
- }
25301
- async function flashAttribute(node, attributeName, duration) {
25302
- node.setAttribute(attributeName, "true");
25303
- await wait(duration);
25304
- node.removeAttribute(attributeName);
25097
+ async function flashAttribute(node, attributeName, duration) {
25098
+ node.setAttribute(attributeName, "true");
25099
+ await wait(duration);
25100
+ node.removeAttribute(attributeName);
25305
25101
  }
25306
25102
  //#endregion
25307
25103
  //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/listbox.js
@@ -25528,168 +25324,10 @@ var optionMixin = createMixin((handle) => {
25528
25324
  ]];
25529
25325
  };
25530
25326
  });
25531
- var Context$1 = ListboxProvider;
25327
+ var Context = ListboxProvider;
25532
25328
  var list = listMixin;
25533
25329
  var option = optionMixin;
25534
25330
  //#endregion
25535
- //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/lib/outside-click.js
25536
- var onOutsideClick = createMixin((handle) => {
25537
- let active = false;
25538
- let handler = () => {};
25539
- let isInsideTarget = () => false;
25540
- let stopPropagation = true;
25541
- handle.addEventListener("insert", (event) => {
25542
- let node = event.node;
25543
- node.ownerDocument.addEventListener("click", (event) => {
25544
- let target = event.target instanceof Node ? event.target : null;
25545
- if (!active || target && (node.contains(target) || isInsideTarget(target))) return;
25546
- if (stopPropagation) event.stopPropagation();
25547
- handler(target);
25548
- }, {
25549
- capture: true,
25550
- signal: handle.signal
25551
- });
25552
- });
25553
- return (nextActive, nextHandler, nextIsInsideTarget = () => false, nextStopPropagation = true) => {
25554
- active = nextActive;
25555
- handler = nextHandler;
25556
- isInsideTarget = nextIsInsideTarget;
25557
- stopPropagation = nextStopPropagation;
25558
- };
25559
- });
25560
- //#endregion
25561
- //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/lib/scroll-lock.js
25562
- var scrollLocks = /* @__PURE__ */ new WeakMap();
25563
- function lockScroll(targetDocument = globalThis.document) {
25564
- if (!targetDocument?.body || !targetDocument.defaultView) return () => {};
25565
- let document = targetDocument;
25566
- let documentElement = document.documentElement;
25567
- let view = document.defaultView;
25568
- let state = scrollLocks.get(document);
25569
- if (!state) {
25570
- let scrollX = view.scrollX;
25571
- let scrollY = view.scrollY;
25572
- let scrollbarWidth = documentElement.clientWidth > 0 ? Math.max(view.innerWidth - documentElement.clientWidth, 0) : 0;
25573
- let computedScrollbarGutter = view.getComputedStyle(documentElement).scrollbarGutter;
25574
- state = {
25575
- count: 0,
25576
- documentOverflow: documentElement.style.overflow,
25577
- documentScrollbarGutter: documentElement.style.scrollbarGutter,
25578
- scrollX,
25579
- scrollY
25580
- };
25581
- documentElement.style.overflow = "hidden";
25582
- if (scrollbarWidth > 0 && computedScrollbarGutter === "auto") documentElement.style.scrollbarGutter = "stable";
25583
- scrollLocks.set(document, state);
25584
- }
25585
- state.count++;
25586
- let unlocked = false;
25587
- return () => {
25588
- if (unlocked) return;
25589
- unlocked = true;
25590
- let currentState = scrollLocks.get(document);
25591
- if (!currentState) return;
25592
- currentState.count--;
25593
- if (currentState.count > 0) return;
25594
- scrollLocks.delete(document);
25595
- documentElement.style.overflow = currentState.documentOverflow;
25596
- documentElement.style.scrollbarGutter = currentState.documentScrollbarGutter;
25597
- view.scrollTo(currentState.scrollX, currentState.scrollY);
25598
- };
25599
- }
25600
- //#endregion
25601
- //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/popover.js
25602
- function PopoverProvider(handle) {
25603
- handle.context.set({
25604
- hideFocusTarget: null,
25605
- showFocusTarget: null,
25606
- surface: null,
25607
- anchor: null
25608
- });
25609
- return () => handle.props.children;
25610
- }
25611
- var anchorMixin = createMixin((handle) => {
25612
- let context = handle.context.get(PopoverProvider);
25613
- return (options) => {
25614
- handle.queueTask((node) => {
25615
- context.anchor = {
25616
- target: node,
25617
- options
25618
- };
25619
- });
25620
- };
25621
- });
25622
- function anchorContains(anchor, target) {
25623
- return anchor?.target instanceof HTMLElement && anchor.target.contains(target);
25624
- }
25625
- var surfaceMixin = createMixin((handle) => {
25626
- let openProp = false;
25627
- let cleanupAnchor = () => {};
25628
- let unlockScroll = () => {};
25629
- let context = handle.context.get(PopoverProvider);
25630
- handle.queueTask((node, signal) => {
25631
- context.surface = node;
25632
- signal.addEventListener("abort", () => {
25633
- if (context.surface === node) context.surface = null;
25634
- });
25635
- });
25636
- return (options) => {
25637
- let wasOpen = openProp;
25638
- openProp = options.open;
25639
- handle.queueTask(async (node) => {
25640
- if (openProp && !wasOpen) node.showPopover();
25641
- else if (!openProp && wasOpen) node.hidePopover();
25642
- });
25643
- return [
25644
- attrs({ popover: "manual" }),
25645
- on$1("beforetoggle", (event) => {
25646
- if (event.newState === "open") {
25647
- let anchor = context.anchor;
25648
- if (!anchor) throw new Error("popover.surface() requires a registered anchor before opening");
25649
- cleanupAnchor = anchor$1(event.currentTarget, anchor.target, anchor.options);
25650
- unlockScroll = lockScroll();
25651
- } else if (event.newState === "closed") {
25652
- cleanupAnchor();
25653
- unlockScroll();
25654
- }
25655
- }),
25656
- on$1("toggle", async (event) => {
25657
- if (event.newState === "open") context.showFocusTarget?.focus();
25658
- else if (event.newState === "closed" && options.restoreFocusOnHide !== false) context.hideFocusTarget?.focus();
25659
- }),
25660
- on$1("keydown", (event) => {
25661
- if (event.key === "Escape") {
25662
- event.preventDefault();
25663
- options.onHide({ reason: "escape-key" });
25664
- }
25665
- }),
25666
- onOutsideClick(openProp, (target) => {
25667
- options.onHide({
25668
- reason: "outside-click",
25669
- target
25670
- });
25671
- }, (target) => options.closeOnAnchorClick === false && anchorContains(context.anchor, target), options.stopOutsideClickPropagation ?? true)
25672
- ];
25673
- };
25674
- });
25675
- var focusOnShowMixin = createMixin((handle) => {
25676
- handle.addEventListener("insert", (event) => {
25677
- let context = handle.context.get(PopoverProvider);
25678
- context.showFocusTarget = event.node;
25679
- });
25680
- });
25681
- var focusOnHideMixin = createMixin((handle) => {
25682
- handle.addEventListener("insert", (event) => {
25683
- let context = handle.context.get(PopoverProvider);
25684
- context.hideFocusTarget = event.node;
25685
- });
25686
- });
25687
- var Context = PopoverProvider;
25688
- var anchor = anchorMixin;
25689
- var surface = surfaceMixin;
25690
- var focusOnHide = focusOnHideMixin;
25691
- var focusOnShow = focusOnShowMixin;
25692
- //#endregion
25693
25331
  //#region ../ui/src/lib/select.tsx
25694
25332
  /** The popup surface shared by selects and menus. */
25695
25333
  var menuSurface = "bud-overlay bud-menu m-0 min-w-44 rounded-lg p-1 text-ink";
@@ -25755,7 +25393,7 @@ function Select(handle) {
25755
25393
  let summary = current.length === 0 ? props.placeholder ?? "" : current.length === 1 ? current[0].label : `${current[0].label} +${current.length - 1}`;
25756
25394
  let leading = current.length === 1 && current[0].icon ? current[0].icon : props.icon;
25757
25395
  let search = props.search ? typeof props.search === "string" ? props.search : t("select.searchPlaceholder") : void 0;
25758
- return /* @__PURE__ */ jsxs(Context, { children: [
25396
+ return /* @__PURE__ */ jsxs(Context$1, { children: [
25759
25397
  /* @__PURE__ */ jsx(HeadlessButton, {
25760
25398
  "aria-label": props.label,
25761
25399
  "aria-haspopup": "listbox",
@@ -25846,7 +25484,7 @@ function Select(handle) {
25846
25484
  })
25847
25485
  ]
25848
25486
  })]
25849
- }) : null, /* @__PURE__ */ jsx(Context$1, {
25487
+ }) : null, /* @__PURE__ */ jsx(Context, {
25850
25488
  ref: (value) => {
25851
25489
  list$2 = value;
25852
25490
  },
@@ -25919,6 +25557,403 @@ function Select(handle) {
25919
25557
  };
25920
25558
  }
25921
25559
  //#endregion
25560
+ //#region ../ui/src/lib/comments/comment-data.ts
25561
+ /**
25562
+ * Takes the first letters of a name's first and last words.
25563
+ * @param name - The author's name.
25564
+ * @returns One or two capital letters, or `?` for an empty name.
25565
+ */
25566
+ function commentInitials(name) {
25567
+ let parts = name.trim().split(/\s+/u).filter(Boolean);
25568
+ return `${parts[0]?.[0] ?? "?"}${parts.length > 1 ? parts.at(-1)[0] : ""}`.toUpperCase();
25569
+ }
25570
+ /**
25571
+ * Gives an author a stable color.
25572
+ * @param id - The author's id.
25573
+ * @returns A hue from 0 to 359.
25574
+ */
25575
+ function commentAuthorHue(id) {
25576
+ let hash = 0;
25577
+ for (let character of id) hash = (hash * 31 + character.codePointAt(0)) % 360;
25578
+ return hash;
25579
+ }
25580
+ //#endregion
25581
+ //#region ../ui/src/lib/comments/comment-thread.tsx
25582
+ /**
25583
+ * Shows an author's initials on a circle in their color.
25584
+ * @param handle - The author, and whether the avatar is the compact size.
25585
+ * @returns The avatar render function.
25586
+ */
25587
+ function CommentAvatar(handle) {
25588
+ return () => {
25589
+ let { author, compact = false, class: className = "" } = handle.props;
25590
+ return /* @__PURE__ */ jsx(Text, {
25591
+ variant: "ui-caption",
25592
+ weight: "medium",
25593
+ class: `flex shrink-0 items-center justify-center rounded-full text-white ${compact ? "size-5" : "size-6"} ${className}`,
25594
+ title: author.name,
25595
+ "data-testid": "comment-avatar",
25596
+ style: { backgroundColor: `oklch(0.55 0.14 ${commentAuthorHue(author.id)})` },
25597
+ children: /* @__PURE__ */ jsx("span", {
25598
+ class: compact ? "scale-[0.833]" : void 0,
25599
+ children: commentInitials(author.name)
25600
+ })
25601
+ });
25602
+ };
25603
+ }
25604
+ //#endregion
25605
+ //#region ../ui/src/lib/dialog.tsx
25606
+ var sizes = {
25607
+ default: "max-w-[35rem]",
25608
+ large: "max-w-[39rem]",
25609
+ wide: "max-w-[51rem]"
25610
+ };
25611
+ /**
25612
+ * The one modal: a native dialog on a dimmed, blurred backdrop whose content is a FieldCard,
25613
+ * raised above the page. It takes focus when it opens, returns it when it closes, and closes on
25614
+ * Escape or a click on the backdrop.
25615
+ * @param handle - The label, size, close handler and content.
25616
+ * @returns The dialog's render function.
25617
+ */
25618
+ function Dialog(handle) {
25619
+ let pressedOutside = false;
25620
+ function close() {
25621
+ if (handle.props.dismissible !== false) handle.props.onClose();
25622
+ }
25623
+ return () => {
25624
+ let { label, size = "default", initialFocus, children } = handle.props;
25625
+ return /* @__PURE__ */ jsx("dialog", {
25626
+ "aria-label": label,
25627
+ class: `fixed inset-0 m-auto flex max-h-full w-full flex-col overflow-hidden border-0 bg-transparent p-3 text-ink transition-[opacity,scale] duration-200 ease-out backdrop:bg-black/25 backdrop:backdrop-blur-sm backdrop:transition-opacity backdrop:duration-200 starting:scale-[0.97] starting:opacity-0 starting:backdrop:opacity-0 motion-reduce:transition-none motion-reduce:backdrop:transition-none sm:p-6 ${sizes[size]}`,
25628
+ mix: [
25629
+ ref((node, signal) => openDialog(node, signal, initialFocus)),
25630
+ on("cancel", (event) => {
25631
+ event.preventDefault();
25632
+ close();
25633
+ }),
25634
+ on("pointerdown", (event) => {
25635
+ pressedOutside = event.target === event.currentTarget;
25636
+ }),
25637
+ on("click", (event) => {
25638
+ if (pressedOutside && event.target === event.currentTarget) close();
25639
+ })
25640
+ ],
25641
+ children
25642
+ });
25643
+ };
25644
+ }
25645
+ /**
25646
+ * Owns a native modal and returns focus once Remix removes its host node.
25647
+ * @param node - The mounted dialog.
25648
+ * @param signal - The host's lifetime, including its removal.
25649
+ * @param initialFocus - An optional selector for the element to focus first.
25650
+ */
25651
+ function openDialog(node, signal, initialFocus) {
25652
+ let document = node.ownerDocument;
25653
+ let previous = document.activeElement;
25654
+ node.showModal();
25655
+ if (initialFocus) queueMicrotask(() => {
25656
+ if (!signal.aborted) node.querySelector(initialFocus)?.focus();
25657
+ });
25658
+ signal.addEventListener("abort", () => {
25659
+ node.close();
25660
+ queueMicrotask(() => {
25661
+ if (previous instanceof HTMLElement && previous.isConnected && [...document.querySelectorAll("dialog:modal")].every((dialog) => dialog.contains(previous))) previous.focus({ preventScroll: true });
25662
+ });
25663
+ }, { once: true });
25664
+ }
25665
+ //#endregion
25666
+ //#region ../ui/src/lib/shortcut.ts
25667
+ var symbols = {
25668
+ arrowup: "↑",
25669
+ arrowdown: "↓",
25670
+ arrowleft: "←",
25671
+ arrowright: "→",
25672
+ enter: "↵",
25673
+ escape: "Esc",
25674
+ backspace: "⌫",
25675
+ tab: "⇥",
25676
+ space: "Space"
25677
+ };
25678
+ /**
25679
+ * Reads a combo such as `mod+shift+k`; the last part is the key, so `mod++` means mod and plus.
25680
+ * @param combo - Modifiers and a key joined by `+`.
25681
+ * @returns The key and its modifiers.
25682
+ */
25683
+ function parseShortcut(combo) {
25684
+ let separator = combo.length < 2 ? -1 : combo.lastIndexOf("+", combo.length - 2);
25685
+ let modifiers = separator < 0 ? [] : combo.slice(0, separator).toLowerCase().split("+");
25686
+ return {
25687
+ key: combo.slice(separator + 1),
25688
+ mod: modifiers.includes("mod"),
25689
+ shift: modifiers.includes("shift"),
25690
+ alt: modifiers.includes("alt")
25691
+ };
25692
+ }
25693
+ /**
25694
+ * The keycaps to show for a combo, in the platform's order and notation.
25695
+ * @param combo - Modifiers and a key joined by `+`.
25696
+ * @param apple - Whether to use Apple symbols; detected from the browser by default.
25697
+ * @returns One label per key.
25698
+ */
25699
+ function shortcutKeys(combo, apple = isApple()) {
25700
+ let { key, mod, shift, alt } = parseShortcut(combo);
25701
+ let label = symbols[key.toLowerCase()] ?? (key.length === 1 ? key.toUpperCase() : key);
25702
+ if (apple) return [
25703
+ alt && "⌥",
25704
+ shift && "⇧",
25705
+ mod && "⌘",
25706
+ label
25707
+ ].filter((part) => part !== false);
25708
+ return [
25709
+ mod && "Ctrl",
25710
+ alt && "Alt",
25711
+ shift && "Shift",
25712
+ label
25713
+ ].filter((part) => part !== false);
25714
+ }
25715
+ function isApple() {
25716
+ return typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.platform);
25717
+ }
25718
+ //#endregion
25719
+ //#region ../ui/src/lib/tooltip.tsx
25720
+ var delay = 400;
25721
+ var warmFor = 300;
25722
+ var closedAt = 0;
25723
+ /**
25724
+ * Names a control on hover or keyboard focus, with its shortcut. The control keeps its own
25725
+ * accessible name; the tooltip describes it.
25726
+ * @param handle - The label, optional shortcut and the control it describes.
25727
+ * @returns The tooltip render function.
25728
+ */
25729
+ function Tooltip(handle) {
25730
+ let open = false;
25731
+ let timer;
25732
+ let trigger;
25733
+ let release = () => {};
25734
+ let id = `${handle.id}-tooltip`;
25735
+ function show(immediately) {
25736
+ clearTimeout(timer);
25737
+ if (open) return;
25738
+ let reveal = () => {
25739
+ open = true;
25740
+ handle.update();
25741
+ };
25742
+ if (immediately || Date.now() - closedAt < warmFor) reveal();
25743
+ else timer = setTimeout(reveal, delay);
25744
+ }
25745
+ function hide() {
25746
+ clearTimeout(timer);
25747
+ if (!open) return;
25748
+ open = false;
25749
+ closedAt = Date.now();
25750
+ handle.update();
25751
+ }
25752
+ return () => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
25753
+ class: "inline-flex",
25754
+ mix: [
25755
+ ref((node, signal) => {
25756
+ trigger = node.firstElementChild instanceof HTMLElement ? node.firstElementChild : node;
25757
+ trigger.setAttribute("aria-describedby", id);
25758
+ let escape = (event) => {
25759
+ if (event.key === "Escape") hide();
25760
+ };
25761
+ document.addEventListener("keydown", escape, { signal });
25762
+ signal.addEventListener("abort", () => clearTimeout(timer), { once: true });
25763
+ }),
25764
+ on("pointerenter", (event) => {
25765
+ if (event.pointerType === "mouse") show(false);
25766
+ }),
25767
+ on("pointerleave", hide),
25768
+ on("pointerdown", hide),
25769
+ on("focusin", (event) => {
25770
+ if (event.target.dataset.focusMethod === "keyboard") show(true);
25771
+ }),
25772
+ on("focusout", hide)
25773
+ ],
25774
+ children: handle.props.children
25775
+ }), /* @__PURE__ */ jsxs("div", {
25776
+ id,
25777
+ role: "tooltip",
25778
+ popover: "manual",
25779
+ class: "bud-tooltip pointer-events-none m-0 inline-flex items-center gap-1.5 rounded-[10px] px-2 py-1.5 transition-[opacity,scale] duration-100 ease-out starting:scale-95 starting:opacity-0 motion-reduce:transition-none",
25780
+ mix: ref((node, signal) => {
25781
+ if (!open || !trigger) return;
25782
+ node.showPopover();
25783
+ release = anchor$1(node, trigger, {
25784
+ placement: handle.props.placement ?? "top",
25785
+ offset: 6
25786
+ });
25787
+ signal.addEventListener("abort", () => {
25788
+ release();
25789
+ if (node.isConnected && node.matches(":popover-open")) node.hidePopover();
25790
+ }, { once: true });
25791
+ }),
25792
+ hidden: !open,
25793
+ children: [/* @__PURE__ */ jsx(Text, {
25794
+ variant: "ui-caption",
25795
+ weight: "medium",
25796
+ children: handle.props.label
25797
+ }), handle.props.shortcut ? /* @__PURE__ */ jsx(Kbd, { shortcut: handle.props.shortcut }) : null]
25798
+ }, open ? "open" : "closed")] });
25799
+ }
25800
+ /**
25801
+ * A shortcut as keycaps, such as ⌘ K, in the platform's notation.
25802
+ * @param handle - A combo such as `mod+shift+k`.
25803
+ * @returns The keycaps render function.
25804
+ */
25805
+ function Kbd(handle) {
25806
+ return () => /* @__PURE__ */ jsx("span", {
25807
+ class: `inline-flex items-center gap-0.5 ${handle.props.class ?? ""}`,
25808
+ children: shortcutKeys(handle.props.shortcut).map((key, index) => /* @__PURE__ */ jsx(Text, {
25809
+ as: "kbd",
25810
+ variant: "kbd",
25811
+ class: "inline-flex h-4.5 min-w-4.5 items-center justify-center rounded-[5px] bg-current/12 px-1 opacity-80",
25812
+ "data-slot": "kbd",
25813
+ children: key
25814
+ }, index))
25815
+ });
25816
+ }
25817
+ //#endregion
25818
+ //#region ../ui/src/lib/icon-button.tsx
25819
+ /**
25820
+ * Compact control shared by the canvas, sidebar, and embedded product demo.
25821
+ * @param handle - Native button props, icon, label, and active state.
25822
+ * @returns The button render function.
25823
+ */
25824
+ function IconButton(handle) {
25825
+ return () => {
25826
+ let { icon, label, active, surface, size = "default", iconSize = size === "small" ? 14 : 16, tooltip, shortcut, class: className = "", ...props } = handle.props;
25827
+ let button = /* @__PURE__ */ jsx(HeadlessButton, {
25828
+ ...props,
25829
+ "aria-label": label,
25830
+ title: tooltip ? void 0 : props.title ?? label,
25831
+ "aria-pressed": props["aria-pressed"] ?? active,
25832
+ "data-active": active,
25833
+ class: `bud-interaction ${surface ? "bud-control" : ""} flex shrink-0 items-center justify-center ${size === "small" ? "size-6 rounded-md" : "size-control rounded-lg"} ${className}`,
25834
+ children: /* @__PURE__ */ jsx(Icon, {
25835
+ name: icon,
25836
+ size: iconSize
25837
+ })
25838
+ });
25839
+ return tooltip ? /* @__PURE__ */ jsx(Tooltip, {
25840
+ label,
25841
+ shortcut,
25842
+ children: button
25843
+ }) : button;
25844
+ };
25845
+ }
25846
+ //#endregion
25847
+ //#region ../ui/src/lib/scroll-fade.ts
25848
+ /**
25849
+ * Fades a scroller's edges while it has more content past them, after Divine's scroll-fade
25850
+ * utility. The fade only applies while the content overflows, so a short list keeps crisp edges.
25851
+ * @param axis - The direction the element scrolls.
25852
+ * @returns A mixin for the scrolling element.
25853
+ */
25854
+ function scrollFade(axis = "x") {
25855
+ return ref((node, signal) => {
25856
+ let update = () => {
25857
+ if (axis === "x" ? node.scrollWidth > node.clientWidth + 1 : node.scrollHeight > node.clientHeight + 1) node.dataset.scrollFade = axis;
25858
+ else delete node.dataset.scrollFade;
25859
+ };
25860
+ let resize = new ResizeObserver(update);
25861
+ let mutation = new MutationObserver(update);
25862
+ resize.observe(node);
25863
+ mutation.observe(node, {
25864
+ childList: true,
25865
+ subtree: true,
25866
+ characterData: true
25867
+ });
25868
+ update();
25869
+ signal.addEventListener("abort", () => {
25870
+ resize.disconnect();
25871
+ mutation.disconnect();
25872
+ }, { once: true });
25873
+ });
25874
+ }
25875
+ //#endregion
25876
+ //#region ../ui/src/lib/workspace.tsx
25877
+ /**
25878
+ * A panel's toolbar: content on the left, such as a path, and actions on the right. On narrow
25879
+ * screens the actions scroll, fading at their edges.
25880
+ * @param handle - The start content, the actions, and whether a line separates it from the
25881
+ * panel, which content that fades as it scrolls doesn't need.
25882
+ * @returns The toolbar render function.
25883
+ */
25884
+ function WorkspaceToolbar(handle) {
25885
+ return () => /* @__PURE__ */ jsxs("div", {
25886
+ class: `bud-toolbar flex h-12 shrink-0 items-center gap-2 ps-2 pe-2 ${handle.props.divider === false ? "" : "border-b border-line"}`,
25887
+ children: [handle.props.start, /* @__PURE__ */ jsx(ToolbarGroup, {
25888
+ class: "ms-auto min-w-0 [scrollbar-width:none] max-md:overflow-x-auto max-md:p-1",
25889
+ mix: scrollFade("x"),
25890
+ children: handle.props.children
25891
+ })]
25892
+ });
25893
+ }
25894
+ /**
25895
+ * A row of toolbar buttons and separators, spaced alike in every toolbar.
25896
+ * @param handle - Native div props and the buttons.
25897
+ * @returns The group render function.
25898
+ */
25899
+ function ToolbarGroup(handle) {
25900
+ return () => {
25901
+ let { class: className = "", children, ...props } = handle.props;
25902
+ return /* @__PURE__ */ jsx("div", {
25903
+ ...props,
25904
+ class: `flex items-center gap-1.5 ${className}`,
25905
+ children
25906
+ });
25907
+ };
25908
+ }
25909
+ //#endregion
25910
+ //#region ../ui/src/lib/toast-store.ts
25911
+ var defaultDuration = 4e3;
25912
+ var items = [];
25913
+ var listeners = /* @__PURE__ */ new Set();
25914
+ var next = 0;
25915
+ function publish() {
25916
+ for (let listener of listeners) listener(items);
25917
+ }
25918
+ /**
25919
+ * Shows a notification; the newest is in front.
25920
+ * @param title - The message.
25921
+ * @param options - A description, type, duration or stable id.
25922
+ * @returns The toast's id, for dismissing it early.
25923
+ */
25924
+ function toast(title, options = {}) {
25925
+ let id = options.id ?? `toast-${++next}`;
25926
+ items = [{
25927
+ id,
25928
+ title,
25929
+ description: options.description,
25930
+ type: options.type ?? "default",
25931
+ duration: options.duration ?? defaultDuration,
25932
+ removed: false
25933
+ }, ...items.filter((existing) => existing.id !== id)];
25934
+ publish();
25935
+ return id;
25936
+ }
25937
+ toast.success = (title, options = {}) => toast(title, {
25938
+ ...options,
25939
+ type: "success"
25940
+ });
25941
+ toast.error = (title, options = {}) => toast(title, {
25942
+ ...options,
25943
+ type: "error"
25944
+ });
25945
+ /**
25946
+ * Starts a toast's exit; the Toaster removes it once it has animated out.
25947
+ * @param id - The toast to dismiss; every toast when omitted.
25948
+ */
25949
+ toast.dismiss = (id) => {
25950
+ items = items.map((item) => id === void 0 || item.id === id ? {
25951
+ ...item,
25952
+ removed: true
25953
+ } : item);
25954
+ publish();
25955
+ };
25956
+ //#endregion
25922
25957
  //#region ../ui/src/lib/tasks/task-draft.ts
25923
25958
  function editValue(value, field) {
25924
25959
  return field.kind === "json" ? JSON.stringify(value ?? {}, null, 2) : taskValueText(value);