@bud.sh/app 0.4.1 → 0.5.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.
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-Cu6R6xxT.js} +1 -1
  3. package/dist/client/assets/difference-worker-jfTSn-Tw.js +8 -0
  4. package/dist/client/assets/{edit-B050x0rV.js → edit-DUotSxjS.js} +12 -12
  5. package/dist/client/assets/files-js6ElfkU.js +1 -0
  6. package/dist/client/assets/i18n-DxqFr2mi.js +1 -0
  7. package/dist/client/assets/index-CFFxP0gS.css +2 -0
  8. package/dist/client/assets/{index-COvyj5zV.js → index-ChANGtl-.js} +2 -2
  9. package/dist/client/assets/lru-DzKYIXMA.js +1 -0
  10. package/dist/client/assets/pages-6_lhiQmz.js +1 -0
  11. package/dist/client/assets/{project-branches-DwKjCwZv.js → project-branches-DO897zM6.js} +1 -1
  12. package/dist/client/assets/project-comments-DFFKVpia.js +1 -0
  13. package/dist/client/assets/project-thread-links-NLFWQRD2.js +1 -0
  14. package/dist/client/assets/{project-threads-DeKXNqeP.js → project-threads-Lay351p4.js} +105 -105
  15. package/dist/client/assets/{project-work-B7PGQtV9.js → project-work-du0Y-L5Y.js} +1 -1
  16. package/dist/client/assets/rolldown-runtime-XKyaHtl9.js +1 -0
  17. package/dist/client/assets/task-design-Avb2RO4w.js +23 -0
  18. package/dist/client/assets/tasks-CA-E8bbK.js +1 -0
  19. package/dist/client/assets/{tasks-request-CsUB5__X.js → tasks-request-C55IeuiP.js} +1 -1
  20. package/dist/client/assets/workspace-Cw5HvX5d.js +2 -0
  21. package/dist/client/index.html +3 -3
  22. package/dist/hosted/assets/{canvas-gWj_ocKK.js → canvas-V36732ER.js} +6 -4
  23. package/dist/hosted/assets/{comment-data-C-rNCB1n.js → comment-data-CF2ZV_3k.js} +4021 -3645
  24. package/dist/hosted/assets/{files-CAPvbavY.js → files-DjSCjfU7.js} +5 -3
  25. package/dist/hosted/assets/{pages-DJiOvT-l.js → pages-BzjzeuPu.js} +3 -3
  26. package/dist/hosted/assets/{project-branches-BgrfdvtL.js → project-branches-BT97J9i7.js} +1 -1
  27. package/dist/hosted/assets/{project-thread-links-B7uzThL9.js → project-thread-links-BRLgByEe.js} +2 -2
  28. package/dist/hosted/assets/{screen-D4D9nh4V.js → screen-CKp2C7kx.js} +68 -137
  29. package/dist/hosted/assets/{tasks-C7aGLzBw.js → tasks-Dj7AyBJB.js} +12 -5
  30. package/dist/hosted/assets/{tasks-request-CikFdCgP.js → tasks-request-DZob623r.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 +829 -789
  34. package/dist/node/server.js +25 -3
  35. package/dist/node-host/assets/{canvas-kXzp80a5.js → canvas-TRUfxfL5.js} +6 -4
  36. package/dist/node-host/assets/{comment-data-CeuJbJKJ.js → comment-data-DyIZNpfG.js} +4003 -3627
  37. package/dist/node-host/assets/{files-0YIuvKzb.js → files-Dh2TiQZq.js} +5 -3
  38. package/dist/node-host/assets/{pages-DOF22SyG.js → pages-BxpjbYhs.js} +3 -3
  39. package/dist/node-host/assets/{project-branches-BeEwMBIw.js → project-branches-B7HUGiVK.js} +1 -1
  40. package/dist/node-host/assets/{project-thread-links-Bs_MtfBC.js → project-thread-links-CauJF0Cz.js} +2 -2
  41. package/dist/node-host/assets/{screen-DRTAKkn3.js → screen-CZXWJEds.js} +68 -137
  42. package/dist/node-host/assets/{tasks-BA_hcbj5.js → tasks-Bs1IOpeU.js} +12 -5
  43. package/dist/node-host/assets/{tasks-request-Doc9y_20.js → tasks-request-ZWmbc8fE.js} +1 -1
  44. package/dist/node-host/node-host.js +164 -16
  45. package/dist/server/assets/{canvas-CiN6jNIZ.js → canvas-Dc1mYCwQ.js} +7 -5
  46. package/dist/server/assets/{comment-data-9SqALQjT.js → comment-data-CsjukwAW.js} +3773 -3608
  47. package/dist/server/assets/{events-6z5v7Uca.js → events-iHpLGr3k.js} +213 -2
  48. package/dist/server/assets/{files-DBVqeLdL.js → files-E9mSFeoc.js} +6 -4
  49. package/dist/server/assets/{listener-DGWKUc2o.js → listener-DN5TyOjJ.js} +35 -9
  50. package/dist/server/assets/{pages-DJMLr6j9.js → pages-BBBXyJWV.js} +4 -4
  51. package/dist/server/assets/{project-branches-DbKEmiQT.js → project-branches-B0YIK6Ur.js} +2 -2
  52. package/dist/server/assets/{project-thread-links-C0kV_Pcl.js → project-thread-links-CBOpf6zk.js} +3 -3
  53. package/dist/server/assets/{project-work-C0FN3w5Y.js → project-work-qSKFO5yj.js} +3 -3
  54. package/dist/server/assets/{screen-BtvzEiIa.js → screen-BIQA2mr4.js} +69 -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-CiXodsLN.js} +14 -7
  58. package/dist/server/assets/{tasks-request-C3jk9Jyu.js → tasks-request-DhwCzpRi.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"
@@ -4053,6 +4061,30 @@ var de = {
4053
4061
  text: "Kommentar",
4054
4062
  source: "984f9ffe"
4055
4063
  },
4064
+ "comments.commentThread.commentActions": {
4065
+ text: "Kommentaraktionen",
4066
+ source: "ebed7451"
4067
+ },
4068
+ "comments.commentThread.confirmDeleteComment": {
4069
+ text: "Diesen Kommentar löschen?",
4070
+ source: "46eacc70"
4071
+ },
4072
+ "comments.commentThread.confirmDeleteDiscussion": {
4073
+ text: "Diese Diskussion löschen?",
4074
+ source: "860d22bf"
4075
+ },
4076
+ "comments.commentThread.delete": {
4077
+ text: "Löschen",
4078
+ source: "5797ea6a"
4079
+ },
4080
+ "comments.commentThread.deleteComment": {
4081
+ text: "Kommentar löschen",
4082
+ source: "d8c741d3"
4083
+ },
4084
+ "comments.commentThread.deleteDiscussion": {
4085
+ text: "Diskussion löschen",
4086
+ source: "af680606"
4087
+ },
4056
4088
  "comments.commentThread.leaveCommentPlaceholder": {
4057
4089
  text: "Kommentar schreiben…",
4058
4090
  source: "98a1ef74"
@@ -7751,11 +7783,13 @@ var en = {
7751
7783
  "app.client.projectComments.adapterDoesNotSupportOperation": "The comment adapter does not support this operation.",
7752
7784
  "app.client.projectComments.adapterUnavailable": "The comment adapter is unavailable.",
7753
7785
  "app.client.projectComments.budUndidCommentChange": "Bud undid a comment change",
7786
+ "app.client.projectComments.commentDeleted": "Comment deleted",
7754
7787
  "app.client.projectComments.commentSaved": "Comment saved",
7755
7788
  "app.client.projectComments.commentWriteNotConfirmed": "The comment write was not confirmed.",
7756
7789
  "app.client.projectComments.commentsSynced": "Comments synced",
7757
7790
  "app.client.projectComments.couldNotLoadComments": "Could not load comments.",
7758
7791
  "app.client.projectComments.couldNotLoadDiscussion": "Could not load the discussion.",
7792
+ "app.client.projectComments.discussionDeleted": "Discussion deleted",
7759
7793
  "app.client.projectComments.discussionNoLongerInView": "This discussion is no longer part of this view.",
7760
7794
  "app.client.projectComments.loadCommentsBeforeSaving": "Load comments before saving.",
7761
7795
  "app.client.projectComments.refreshBeforeTryingAgain": "The comment write could not be confirmed. Refresh before trying again.",
@@ -7977,6 +8011,12 @@ var en = {
7977
8011
  "comments.commentPins.detachedTitle": "{name} · Its element is no longer on the page",
7978
8012
  "comments.commentThread.cancel": "Cancel",
7979
8013
  "comments.commentThread.comment": "Comment",
8014
+ "comments.commentThread.commentActions": "Comment actions",
8015
+ "comments.commentThread.confirmDeleteComment": "Delete this comment?",
8016
+ "comments.commentThread.confirmDeleteDiscussion": "Delete this discussion?",
8017
+ "comments.commentThread.delete": "Delete",
8018
+ "comments.commentThread.deleteComment": "Delete comment",
8019
+ "comments.commentThread.deleteDiscussion": "Delete discussion",
7980
8020
  "comments.commentThread.leaveCommentPlaceholder": "Leave a comment…",
7981
8021
  "comments.commentThread.loadingDiscussion": "Loading discussion…",
7982
8022
  "comments.commentThread.openDiscussion": "Open discussion",
@@ -24192,131 +24232,202 @@ function Breadcrumb(handle) {
24192
24232
  };
24193
24233
  }
24194
24234
  //#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
- });
24235
+ //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/core/vnode.js
24236
+ function createRemixElement(type, props, key) {
24237
+ return {
24238
+ $rmx: true,
24239
+ key,
24240
+ props: normalizeElementProps(props),
24241
+ type
24210
24242
  };
24211
24243
  }
24244
+ function isRemixElement(node) {
24245
+ if (typeof node !== "object" || node === null) return false;
24246
+ let type = Reflect.get(node, "type");
24247
+ let props = Reflect.get(node, "props");
24248
+ return Reflect.get(node, "$rmx") === true && (typeof type === "string" || typeof type === "function") && typeof props === "object" && props !== null;
24249
+ }
24250
+ function normalizeElementProps(props) {
24251
+ if (!props) return {};
24252
+ if (!("mix" in props)) return props;
24253
+ if (isNormalizedMix(props.mix)) return props;
24254
+ let { mix, ...rest } = props;
24255
+ let normalizedMix = normalizeMixValue(mix);
24256
+ return normalizedMix === void 0 ? rest : {
24257
+ ...rest,
24258
+ mix: normalizedMix
24259
+ };
24260
+ }
24261
+ function isNormalizedMix(mix) {
24262
+ if (!Array.isArray(mix) || mix.length === 0) return false;
24263
+ for (let i = 0; i < mix.length; i++) {
24264
+ let item = mix[i];
24265
+ if (!item || Array.isArray(item)) return false;
24266
+ }
24267
+ return true;
24268
+ }
24269
+ function normalizeMixValue(mix) {
24270
+ if (!mix) return void 0;
24271
+ let normalizedMix = [];
24272
+ flattenMixValue(mix, normalizedMix);
24273
+ return normalizedMix.length === 0 ? void 0 : normalizedMix;
24274
+ }
24275
+ function flattenMixValue(mix, out) {
24276
+ if (!mix) return;
24277
+ if (!Array.isArray(mix)) {
24278
+ out.push(mix);
24279
+ return;
24280
+ }
24281
+ for (let item of mix) flattenMixValue(item, out);
24282
+ }
24212
24283
  //#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();
24284
+ //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/jsx.js
24285
+ function jsx$1(type, props, key) {
24286
+ return createRemixElement(type, props, key);
24222
24287
  }
24288
+ //#endregion
24289
+ //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/invariant.js
24223
24290
  /**
24224
- * Gives an author a stable color.
24225
- * @param id - The author's id.
24226
- * @returns A hue from 0 to 359.
24291
+ * Use this when framework code is incorrect, indicating an internal bug rather
24292
+ * than application code error.
24293
+ *
24294
+ * @param assertion The value to check for truthiness
24295
+ * @param message Optional message to include in the error
24227
24296
  */
24228
- function commentAuthorHue(id) {
24229
- let hash = 0;
24230
- for (let character of id) hash = (hash * 31 + character.codePointAt(0)) % 360;
24231
- return hash;
24297
+ function invariant(assertion, message) {
24298
+ let prefix = "Framework invariant";
24299
+ if (assertion) return;
24300
+ throw new Error(message ? `${prefix}: ${message}` : prefix);
24232
24301
  }
24233
24302
  //#endregion
24234
- //#region ../ui/src/lib/comments/comment-thread.tsx
24303
+ //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/core/children.js
24304
+ function normalizeChildren(children) {
24305
+ for (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return children.flat(Infinity);
24306
+ return children;
24307
+ }
24308
+ function assertRemixNodes(values) {
24309
+ for (let value of values) invariant(isRemixNode(value), "Invalid child node");
24310
+ }
24311
+ function isRemixNode(value) {
24312
+ if (value == null) return true;
24313
+ let type = typeof value;
24314
+ if (type === "string" || type === "number" || type === "bigint" || type === "boolean") return true;
24315
+ if (isRemixElement(value)) return true;
24316
+ if (!Array.isArray(value)) return false;
24317
+ for (let child of value) if (!isRemixNode(child)) return false;
24318
+ return true;
24319
+ }
24320
+ //#endregion
24321
+ //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/mixins/mixin.js
24235
24322
  /**
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.
24323
+ * Creates a typed mixin factory that can be passed through the `mix` prop.
24324
+ *
24325
+ * @param type Mixin setup function.
24326
+ * @returns A function that captures mixin arguments and returns a descriptor.
24239
24327
  */
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
- };
24328
+ function createMixin(type) {
24329
+ return (...args) => ({
24330
+ type,
24331
+ args
24332
+ });
24256
24333
  }
24257
24334
  //#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]"
24335
+ //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/mixins/on-mixin.js
24336
+ var onMixinType = (handle) => {
24337
+ let currentHandler = () => {};
24338
+ let currentType = "";
24339
+ let currentCapture = false;
24340
+ let currentNode = null;
24341
+ let reentry = null;
24342
+ let stableHandler = (event) => {
24343
+ reentry?.abort(new DOMException("", "EventReentry"));
24344
+ reentry = new AbortController();
24345
+ currentHandler(event, reentry.signal);
24346
+ };
24347
+ handle.addEventListener("insert", (event) => {
24348
+ currentNode = event.node;
24349
+ currentNode.addEventListener(currentType, stableHandler, currentCapture);
24350
+ });
24351
+ handle.addEventListener("remove", () => {
24352
+ currentNode?.removeEventListener(currentType, stableHandler, currentCapture);
24353
+ currentNode = null;
24354
+ reentry?.abort(new DOMException("", "AbortError"));
24355
+ });
24356
+ return (type, handler, captureBoolean = false) => {
24357
+ let previousType = currentType;
24358
+ let previousCapture = currentCapture;
24359
+ let needsRebind = currentType !== type || currentCapture !== captureBoolean;
24360
+ currentType = type;
24361
+ currentHandler = handler;
24362
+ currentCapture = captureBoolean;
24363
+ if (needsRebind && currentNode) {
24364
+ currentNode.removeEventListener(previousType, stableHandler, previousCapture);
24365
+ currentNode.addEventListener(type, stableHandler, captureBoolean);
24366
+ }
24367
+ return handle.element;
24368
+ };
24263
24369
  };
24370
+ var onMixin = createMixin(onMixinType);
24264
24371
  /**
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.
24372
+ * Attaches a typed DOM event handler through the mixin system.
24373
+ *
24374
+ * @param type Event type to listen for.
24375
+ * @param handler Event handler.
24376
+ * @param captureBoolean Whether to listen during capture.
24377
+ * @returns A mixin descriptor for the target element.
24270
24378
  */
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
- };
24379
+ function on$1(type, handler, captureBoolean) {
24380
+ return onMixin(type, handler, captureBoolean);
24297
24381
  }
24382
+ //#endregion
24383
+ //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/create-element.js
24298
24384
  /**
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.
24385
+ * Creates a Remix virtual element from a JSX-like call signature.
24386
+ *
24387
+ * @param type Host tag, component function, or mixin host placeholder.
24388
+ * @param props Element props.
24389
+ * @param children Child nodes.
24390
+ * @returns A Remix virtual element.
24303
24391
  */
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 });
24317
- }
24318
- //#endregion
24319
- //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/anchor.js
24392
+ function createElement$1(type, props, ...children) {
24393
+ let nextProps = { ...props ?? {} };
24394
+ if (isMixinElementType(type)) {
24395
+ if (children.length > 0) console.error(/* @__PURE__ */ new Error("mixin elements must not receive children"));
24396
+ } else {
24397
+ assertRemixNodes(children);
24398
+ nextProps.children = normalizeChildren(children);
24399
+ }
24400
+ if (nextProps.key != null) {
24401
+ let { key, ...rest } = nextProps;
24402
+ return jsx$1(type, rest, key);
24403
+ }
24404
+ return jsx$1(type, nextProps);
24405
+ }
24406
+ function isMixinElementType(type) {
24407
+ return typeof type === "function" && "__rmxMixinElementType" in type;
24408
+ }
24409
+ //#endregion
24410
+ //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/mixins/attrs-mixin.js
24411
+ var attrsMixin = createMixin((handle) => (defaults, props) => {
24412
+ let nextProps = props;
24413
+ for (let key in defaults) {
24414
+ if (props[key] !== void 0) continue;
24415
+ if (nextProps === props) nextProps = { ...props };
24416
+ nextProps[key] = defaults[key];
24417
+ }
24418
+ return nextProps === props ? handle.element : createElement$1(handle.element, nextProps);
24419
+ });
24420
+ /**
24421
+ * Applies default host props unless the element already provides them explicitly.
24422
+ *
24423
+ * @param defaults Default props to apply when the element does not already define them.
24424
+ * @returns A mixin descriptor that provides default host props.
24425
+ */
24426
+ function attrs(defaults) {
24427
+ return attrsMixin(defaults);
24428
+ }
24429
+ //#endregion
24430
+ //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/anchor.js
24320
24431
  var viewportPaddingPx = 16;
24321
24432
  function isHorizontalPlacement(placement) {
24322
24433
  return placement.startsWith("left") || placement.startsWith("right");
@@ -24760,548 +24871,238 @@ function anchor$1(floating, anchorTarget, options = {}) {
24760
24871
  };
24761
24872
  }
24762
24873
  //#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")
24874
+ //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/lib/outside-click.js
24875
+ var onOutsideClick = createMixin((handle) => {
24876
+ let active = false;
24877
+ let handler = () => {};
24878
+ let isInsideTarget = () => false;
24879
+ let stopPropagation = true;
24880
+ handle.addEventListener("insert", (event) => {
24881
+ let node = event.node;
24882
+ node.ownerDocument.addEventListener("click", (event) => {
24883
+ let target = event.target instanceof Node ? event.target : null;
24884
+ if (!active || target && (node.contains(target) || isInsideTarget(target))) return;
24885
+ if (stopPropagation) event.stopPropagation();
24886
+ handler(target);
24887
+ }, {
24888
+ capture: true,
24889
+ signal: handle.signal
24890
+ });
24891
+ });
24892
+ return (nextActive, nextHandler, nextIsInsideTarget = () => false, nextStopPropagation = true) => {
24893
+ active = nextActive;
24894
+ handler = nextHandler;
24895
+ isInsideTarget = nextIsInsideTarget;
24896
+ stopPropagation = nextStopPropagation;
24788
24897
  };
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
- }
24898
+ });
24815
24899
  //#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();
24900
+ //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/lib/scroll-lock.js
24901
+ var scrollLocks = /* @__PURE__ */ new WeakMap();
24902
+ function lockScroll(targetDocument = globalThis.document) {
24903
+ if (!targetDocument?.body || !targetDocument.defaultView) return () => {};
24904
+ let document = targetDocument;
24905
+ let documentElement = document.documentElement;
24906
+ let view = document.defaultView;
24907
+ let state = scrollLocks.get(document);
24908
+ if (!state) {
24909
+ let scrollX = view.scrollX;
24910
+ let scrollY = view.scrollY;
24911
+ let scrollbarWidth = documentElement.clientWidth > 0 ? Math.max(view.innerWidth - documentElement.clientWidth, 0) : 0;
24912
+ let computedScrollbarGutter = view.getComputedStyle(documentElement).scrollbarGutter;
24913
+ state = {
24914
+ count: 0,
24915
+ documentOverflow: documentElement.style.overflow,
24916
+ documentScrollbarGutter: documentElement.style.scrollbarGutter,
24917
+ scrollX,
24918
+ scrollY
24838
24919
  };
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();
24920
+ documentElement.style.overflow = "hidden";
24921
+ if (scrollbarWidth > 0 && computedScrollbarGutter === "auto") documentElement.style.scrollbarGutter = "stable";
24922
+ scrollLocks.set(document, state);
24848
24923
  }
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")] });
24924
+ state.count++;
24925
+ let unlocked = false;
24926
+ return () => {
24927
+ if (unlocked) return;
24928
+ unlocked = true;
24929
+ let currentState = scrollLocks.get(document);
24930
+ if (!currentState) return;
24931
+ currentState.count--;
24932
+ if (currentState.count > 0) return;
24933
+ scrollLocks.delete(document);
24934
+ documentElement.style.overflow = currentState.documentOverflow;
24935
+ documentElement.style.scrollbarGutter = currentState.documentScrollbarGutter;
24936
+ view.scrollTo(currentState.scrollX, currentState.scrollY);
24937
+ };
24896
24938
  }
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))
24939
+ //#endregion
24940
+ //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/popover.js
24941
+ function PopoverProvider(handle) {
24942
+ handle.context.set({
24943
+ hideFocusTarget: null,
24944
+ showFocusTarget: null,
24945
+ surface: null,
24946
+ anchor: null
24912
24947
  });
24948
+ return () => handle.props.children;
24913
24949
  }
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
- })
24950
+ var anchorMixin = createMixin((handle) => {
24951
+ let context = handle.context.get(PopoverProvider);
24952
+ return (options) => {
24953
+ handle.queueTask((node) => {
24954
+ context.anchor = {
24955
+ target: node,
24956
+ options
24957
+ };
24935
24958
  });
24936
- return tooltip ? /* @__PURE__ */ jsx(Tooltip, {
24937
- label,
24938
- shortcut,
24939
- children: button
24940
- }) : button;
24941
24959
  };
24960
+ });
24961
+ function anchorContains(anchor, target) {
24962
+ return anchor?.target instanceof HTMLElement && anchor.target.contains(target);
24942
24963
  }
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();
24964
+ var surfaceMixin = createMixin((handle) => {
24965
+ let openProp = false;
24966
+ let cleanupAnchor = () => {};
24967
+ let unlockScroll = () => {};
24968
+ let context = handle.context.get(PopoverProvider);
24969
+ handle.queueTask((node, signal) => {
24970
+ context.surface = node;
24966
24971
  signal.addEventListener("abort", () => {
24967
- resize.disconnect();
24968
- mutation.disconnect();
24969
- }, { once: true });
24972
+ if (context.surface === node) context.surface = null;
24973
+ });
24970
24974
  });
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
- })]
24975
+ return (options) => {
24976
+ let wasOpen = openProp;
24977
+ openProp = options.open;
24978
+ handle.queueTask(async (node) => {
24979
+ if (openProp && !wasOpen) node.showPopover();
24980
+ else if (!openProp && wasOpen) node.hidePopover();
24981
+ });
24982
+ return [
24983
+ attrs({ popover: "manual" }),
24984
+ on$1("beforetoggle", (event) => {
24985
+ if (event.newState === "open") {
24986
+ let anchor = context.anchor;
24987
+ if (!anchor) throw new Error("popover.surface() requires a registered anchor before opening");
24988
+ cleanupAnchor = anchor$1(event.currentTarget, anchor.target, anchor.options);
24989
+ unlockScroll = lockScroll();
24990
+ } else if (event.newState === "closed") {
24991
+ cleanupAnchor();
24992
+ unlockScroll();
24993
+ }
24994
+ }),
24995
+ on$1("toggle", async (event) => {
24996
+ if (event.newState === "open") context.showFocusTarget?.focus();
24997
+ else if (event.newState === "closed" && options.restoreFocusOnHide !== false) context.hideFocusTarget?.focus();
24998
+ }),
24999
+ on$1("keydown", (event) => {
25000
+ if (event.key === "Escape") {
25001
+ event.preventDefault();
25002
+ options.onHide({ reason: "escape-key" });
25003
+ }
25004
+ }),
25005
+ onOutsideClick(openProp, (target) => {
25006
+ options.onHide({
25007
+ reason: "outside-click",
25008
+ target
25009
+ });
25010
+ }, (target) => options.closeOnAnchorClick === false && anchorContains(context.anchor, target), options.stopOutsideClickPropagation ?? true)
25011
+ ];
25012
+ };
25013
+ });
25014
+ var focusOnShowMixin = createMixin((handle) => {
25015
+ handle.addEventListener("insert", (event) => {
25016
+ let context = handle.context.get(PopoverProvider);
25017
+ context.showFocusTarget = event.node;
25018
+ });
25019
+ });
25020
+ var focusOnHideMixin = createMixin((handle) => {
25021
+ handle.addEventListener("insert", (event) => {
25022
+ let context = handle.context.get(PopoverProvider);
25023
+ context.hideFocusTarget = event.node;
24989
25024
  });
25025
+ });
25026
+ var Context$1 = PopoverProvider;
25027
+ var anchor = anchorMixin;
25028
+ var surface = surfaceMixin;
25029
+ var focusOnHide = focusOnHideMixin;
25030
+ var focusOnShow = focusOnShowMixin;
25031
+ //#endregion
25032
+ //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/lib/typeahead.js
25033
+ var HIDDEN_TYPEAHEAD_TIMEOUT = 750;
25034
+ var hiddenTypeahead = createMixin((handle) => {
25035
+ let text = "";
25036
+ let timeoutId = 0;
25037
+ function clearTypeahead() {
25038
+ clearTimeout(timeoutId);
25039
+ timeoutId = 0;
25040
+ text = "";
25041
+ }
25042
+ function updateTypeahead(nextText, onTypeahead) {
25043
+ text = nextText;
25044
+ onTypeahead(text);
25045
+ }
25046
+ handle.addEventListener("remove", clearTypeahead);
25047
+ return (onTypeahead) => [on$1("focusout", (event) => {
25048
+ if (event.relatedTarget instanceof Node && event.currentTarget.contains(event.relatedTarget)) return;
25049
+ clearTypeahead();
25050
+ }), on$1("keydown", (event) => {
25051
+ if (event.key.length === 1 && !event.ctrlKey && !event.altKey && !event.metaKey) {
25052
+ clearTimeout(timeoutId);
25053
+ updateTypeahead(text + event.key.toLowerCase(), onTypeahead);
25054
+ timeoutId = window.setTimeout(clearTypeahead, HIDDEN_TYPEAHEAD_TIMEOUT);
25055
+ } else if (event.key === "Escape") clearTypeahead();
25056
+ else if (event.key === "Backspace" && text.length > 0) {
25057
+ clearTimeout(timeoutId);
25058
+ updateTypeahead(text.slice(0, -1), onTypeahead);
25059
+ timeoutId = window.setTimeout(clearTypeahead, HIDDEN_TYPEAHEAD_TIMEOUT);
25060
+ }
25061
+ })];
25062
+ });
25063
+ function itemMatchesSearchText(item, text, getSearchValues) {
25064
+ let values = getSearchValues(item);
25065
+ let searchValues = Array.isArray(values) ? values : [values];
25066
+ let normalizedText = text.toLowerCase();
25067
+ return searchValues.some((value) => value.toLowerCase().startsWith(normalizedText));
25068
+ }
25069
+ function matchNextItemBySearchText(text, items, options) {
25070
+ if (text === "") return null;
25071
+ for (let offset = 1; offset <= items.length; offset++) {
25072
+ let item = items[(options.fromIndex + offset + items.length) % items.length];
25073
+ if (itemMatchesSearchText(item, text, options.getSearchValues)) return item;
25074
+ }
25075
+ return null;
24990
25076
  }
25077
+ //#endregion
25078
+ //#region ../ui/src/lib/headless-button.tsx
24991
25079
  /**
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.
25080
+ * A native button with shared interactions; callers own its content and geometry.
25081
+ * @param handle - Native button props, content, and Remix mixins.
25082
+ * @returns The button render function.
24995
25083
  */
24996
- function ToolbarGroup(handle) {
25084
+ function HeadlessButton(handle) {
24997
25085
  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
25086
+ let { class: className, type = "button", ...props } = handle.props;
25087
+ let attributes = ariaAttributes(props);
25088
+ return /* @__PURE__ */ jsx("button", {
25089
+ ...attributes,
25090
+ type,
25091
+ class: ["bud-headless-button", className].filter(Boolean).join(" ")
25003
25092
  });
25004
25093
  };
25005
25094
  }
25006
25095
  //#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);
25096
+ //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/lib/flash-attribute.js
25097
+ function wait(ms) {
25098
+ return new Promise((resolve) => {
25099
+ setTimeout(resolve, ms);
25100
+ });
25014
25101
  }
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);
25102
+ async function flashAttribute(node, attributeName, duration) {
25103
+ node.setAttribute(attributeName, "true");
25104
+ await wait(duration);
25105
+ node.removeAttribute(attributeName);
25305
25106
  }
25306
25107
  //#endregion
25307
25108
  //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/listbox.js
@@ -25528,168 +25329,10 @@ var optionMixin = createMixin((handle) => {
25528
25329
  ]];
25529
25330
  };
25530
25331
  });
25531
- var Context$1 = ListboxProvider;
25332
+ var Context = ListboxProvider;
25532
25333
  var list = listMixin;
25533
25334
  var option = optionMixin;
25534
25335
  //#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
25336
  //#region ../ui/src/lib/select.tsx
25694
25337
  /** The popup surface shared by selects and menus. */
25695
25338
  var menuSurface = "bud-overlay bud-menu m-0 min-w-44 rounded-lg p-1 text-ink";
@@ -25755,7 +25398,7 @@ function Select(handle) {
25755
25398
  let summary = current.length === 0 ? props.placeholder ?? "" : current.length === 1 ? current[0].label : `${current[0].label} +${current.length - 1}`;
25756
25399
  let leading = current.length === 1 && current[0].icon ? current[0].icon : props.icon;
25757
25400
  let search = props.search ? typeof props.search === "string" ? props.search : t("select.searchPlaceholder") : void 0;
25758
- return /* @__PURE__ */ jsxs(Context, { children: [
25401
+ return /* @__PURE__ */ jsxs(Context$1, { children: [
25759
25402
  /* @__PURE__ */ jsx(HeadlessButton, {
25760
25403
  "aria-label": props.label,
25761
25404
  "aria-haspopup": "listbox",
@@ -25846,7 +25489,7 @@ function Select(handle) {
25846
25489
  })
25847
25490
  ]
25848
25491
  })]
25849
- }) : null, /* @__PURE__ */ jsx(Context$1, {
25492
+ }) : null, /* @__PURE__ */ jsx(Context, {
25850
25493
  ref: (value) => {
25851
25494
  list$2 = value;
25852
25495
  },
@@ -25919,6 +25562,403 @@ function Select(handle) {
25919
25562
  };
25920
25563
  }
25921
25564
  //#endregion
25565
+ //#region ../ui/src/lib/comments/comment-data.ts
25566
+ /**
25567
+ * Takes the first letters of a name's first and last words.
25568
+ * @param name - The author's name.
25569
+ * @returns One or two capital letters, or `?` for an empty name.
25570
+ */
25571
+ function commentInitials(name) {
25572
+ let parts = name.trim().split(/\s+/u).filter(Boolean);
25573
+ return `${parts[0]?.[0] ?? "?"}${parts.length > 1 ? parts.at(-1)[0] : ""}`.toUpperCase();
25574
+ }
25575
+ /**
25576
+ * Gives an author a stable color.
25577
+ * @param id - The author's id.
25578
+ * @returns A hue from 0 to 359.
25579
+ */
25580
+ function commentAuthorHue(id) {
25581
+ let hash = 0;
25582
+ for (let character of id) hash = (hash * 31 + character.codePointAt(0)) % 360;
25583
+ return hash;
25584
+ }
25585
+ //#endregion
25586
+ //#region ../ui/src/lib/comments/comment-thread.tsx
25587
+ /**
25588
+ * Shows an author's initials on a circle in their color.
25589
+ * @param handle - The author, and whether the avatar is the compact size.
25590
+ * @returns The avatar render function.
25591
+ */
25592
+ function CommentAvatar(handle) {
25593
+ return () => {
25594
+ let { author, compact = false, class: className = "" } = handle.props;
25595
+ return /* @__PURE__ */ jsx(Text, {
25596
+ variant: "ui-caption",
25597
+ weight: "medium",
25598
+ class: `flex shrink-0 items-center justify-center rounded-full text-white ${compact ? "size-5" : "size-6"} ${className}`,
25599
+ title: author.name,
25600
+ "data-testid": "comment-avatar",
25601
+ style: { backgroundColor: `oklch(0.55 0.14 ${commentAuthorHue(author.id)})` },
25602
+ children: /* @__PURE__ */ jsx("span", {
25603
+ class: compact ? "scale-[0.833]" : void 0,
25604
+ children: commentInitials(author.name)
25605
+ })
25606
+ });
25607
+ };
25608
+ }
25609
+ //#endregion
25610
+ //#region ../ui/src/lib/dialog.tsx
25611
+ var sizes = {
25612
+ default: "max-w-[35rem]",
25613
+ large: "max-w-[39rem]",
25614
+ wide: "max-w-[51rem]"
25615
+ };
25616
+ /**
25617
+ * The one modal: a native dialog on a dimmed, blurred backdrop whose content is a FieldCard,
25618
+ * raised above the page. It takes focus when it opens, returns it when it closes, and closes on
25619
+ * Escape or a click on the backdrop.
25620
+ * @param handle - The label, size, close handler and content.
25621
+ * @returns The dialog's render function.
25622
+ */
25623
+ function Dialog(handle) {
25624
+ let pressedOutside = false;
25625
+ function close() {
25626
+ if (handle.props.dismissible !== false) handle.props.onClose();
25627
+ }
25628
+ return () => {
25629
+ let { label, size = "default", initialFocus, children } = handle.props;
25630
+ return /* @__PURE__ */ jsx("dialog", {
25631
+ "aria-label": label,
25632
+ 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]}`,
25633
+ mix: [
25634
+ ref((node, signal) => openDialog(node, signal, initialFocus)),
25635
+ on("cancel", (event) => {
25636
+ event.preventDefault();
25637
+ close();
25638
+ }),
25639
+ on("pointerdown", (event) => {
25640
+ pressedOutside = event.target === event.currentTarget;
25641
+ }),
25642
+ on("click", (event) => {
25643
+ if (pressedOutside && event.target === event.currentTarget) close();
25644
+ })
25645
+ ],
25646
+ children
25647
+ });
25648
+ };
25649
+ }
25650
+ /**
25651
+ * Owns a native modal and returns focus once Remix removes its host node.
25652
+ * @param node - The mounted dialog.
25653
+ * @param signal - The host's lifetime, including its removal.
25654
+ * @param initialFocus - An optional selector for the element to focus first.
25655
+ */
25656
+ function openDialog(node, signal, initialFocus) {
25657
+ let document = node.ownerDocument;
25658
+ let previous = document.activeElement;
25659
+ node.showModal();
25660
+ if (initialFocus) queueMicrotask(() => {
25661
+ if (!signal.aborted) node.querySelector(initialFocus)?.focus();
25662
+ });
25663
+ signal.addEventListener("abort", () => {
25664
+ node.close();
25665
+ queueMicrotask(() => {
25666
+ if (previous instanceof HTMLElement && previous.isConnected && [...document.querySelectorAll("dialog:modal")].every((dialog) => dialog.contains(previous))) previous.focus({ preventScroll: true });
25667
+ });
25668
+ }, { once: true });
25669
+ }
25670
+ //#endregion
25671
+ //#region ../ui/src/lib/shortcut.ts
25672
+ var symbols = {
25673
+ arrowup: "↑",
25674
+ arrowdown: "↓",
25675
+ arrowleft: "←",
25676
+ arrowright: "→",
25677
+ enter: "↵",
25678
+ escape: "Esc",
25679
+ backspace: "⌫",
25680
+ tab: "⇥",
25681
+ space: "Space"
25682
+ };
25683
+ /**
25684
+ * Reads a combo such as `mod+shift+k`; the last part is the key, so `mod++` means mod and plus.
25685
+ * @param combo - Modifiers and a key joined by `+`.
25686
+ * @returns The key and its modifiers.
25687
+ */
25688
+ function parseShortcut(combo) {
25689
+ let separator = combo.length < 2 ? -1 : combo.lastIndexOf("+", combo.length - 2);
25690
+ let modifiers = separator < 0 ? [] : combo.slice(0, separator).toLowerCase().split("+");
25691
+ return {
25692
+ key: combo.slice(separator + 1),
25693
+ mod: modifiers.includes("mod"),
25694
+ shift: modifiers.includes("shift"),
25695
+ alt: modifiers.includes("alt")
25696
+ };
25697
+ }
25698
+ /**
25699
+ * The keycaps to show for a combo, in the platform's order and notation.
25700
+ * @param combo - Modifiers and a key joined by `+`.
25701
+ * @param apple - Whether to use Apple symbols; detected from the browser by default.
25702
+ * @returns One label per key.
25703
+ */
25704
+ function shortcutKeys(combo, apple = isApple()) {
25705
+ let { key, mod, shift, alt } = parseShortcut(combo);
25706
+ let label = symbols[key.toLowerCase()] ?? (key.length === 1 ? key.toUpperCase() : key);
25707
+ if (apple) return [
25708
+ alt && "⌥",
25709
+ shift && "⇧",
25710
+ mod && "⌘",
25711
+ label
25712
+ ].filter((part) => part !== false);
25713
+ return [
25714
+ mod && "Ctrl",
25715
+ alt && "Alt",
25716
+ shift && "Shift",
25717
+ label
25718
+ ].filter((part) => part !== false);
25719
+ }
25720
+ function isApple() {
25721
+ return typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.platform);
25722
+ }
25723
+ //#endregion
25724
+ //#region ../ui/src/lib/tooltip.tsx
25725
+ var delay = 400;
25726
+ var warmFor = 300;
25727
+ var closedAt = 0;
25728
+ /**
25729
+ * Names a control on hover or keyboard focus, with its shortcut. The control keeps its own
25730
+ * accessible name; the tooltip describes it.
25731
+ * @param handle - The label, optional shortcut and the control it describes.
25732
+ * @returns The tooltip render function.
25733
+ */
25734
+ function Tooltip(handle) {
25735
+ let open = false;
25736
+ let timer;
25737
+ let trigger;
25738
+ let release = () => {};
25739
+ let id = `${handle.id}-tooltip`;
25740
+ function show(immediately) {
25741
+ clearTimeout(timer);
25742
+ if (open) return;
25743
+ let reveal = () => {
25744
+ open = true;
25745
+ handle.update();
25746
+ };
25747
+ if (immediately || Date.now() - closedAt < warmFor) reveal();
25748
+ else timer = setTimeout(reveal, delay);
25749
+ }
25750
+ function hide() {
25751
+ clearTimeout(timer);
25752
+ if (!open) return;
25753
+ open = false;
25754
+ closedAt = Date.now();
25755
+ handle.update();
25756
+ }
25757
+ return () => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
25758
+ class: "inline-flex",
25759
+ mix: [
25760
+ ref((node, signal) => {
25761
+ trigger = node.firstElementChild instanceof HTMLElement ? node.firstElementChild : node;
25762
+ trigger.setAttribute("aria-describedby", id);
25763
+ let escape = (event) => {
25764
+ if (event.key === "Escape") hide();
25765
+ };
25766
+ document.addEventListener("keydown", escape, { signal });
25767
+ signal.addEventListener("abort", () => clearTimeout(timer), { once: true });
25768
+ }),
25769
+ on("pointerenter", (event) => {
25770
+ if (event.pointerType === "mouse") show(false);
25771
+ }),
25772
+ on("pointerleave", hide),
25773
+ on("pointerdown", hide),
25774
+ on("focusin", (event) => {
25775
+ if (event.target.dataset.focusMethod === "keyboard") show(true);
25776
+ }),
25777
+ on("focusout", hide)
25778
+ ],
25779
+ children: handle.props.children
25780
+ }), /* @__PURE__ */ jsxs("div", {
25781
+ id,
25782
+ role: "tooltip",
25783
+ popover: "manual",
25784
+ 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",
25785
+ mix: ref((node, signal) => {
25786
+ if (!open || !trigger) return;
25787
+ node.showPopover();
25788
+ release = anchor$1(node, trigger, {
25789
+ placement: handle.props.placement ?? "top",
25790
+ offset: 6
25791
+ });
25792
+ signal.addEventListener("abort", () => {
25793
+ release();
25794
+ if (node.isConnected && node.matches(":popover-open")) node.hidePopover();
25795
+ }, { once: true });
25796
+ }),
25797
+ hidden: !open,
25798
+ children: [/* @__PURE__ */ jsx(Text, {
25799
+ variant: "ui-caption",
25800
+ weight: "medium",
25801
+ children: handle.props.label
25802
+ }), handle.props.shortcut ? /* @__PURE__ */ jsx(Kbd, { shortcut: handle.props.shortcut }) : null]
25803
+ }, open ? "open" : "closed")] });
25804
+ }
25805
+ /**
25806
+ * A shortcut as keycaps, such as ⌘ K, in the platform's notation.
25807
+ * @param handle - A combo such as `mod+shift+k`.
25808
+ * @returns The keycaps render function.
25809
+ */
25810
+ function Kbd(handle) {
25811
+ return () => /* @__PURE__ */ jsx("span", {
25812
+ class: `inline-flex items-center gap-0.5 ${handle.props.class ?? ""}`,
25813
+ children: shortcutKeys(handle.props.shortcut).map((key, index) => /* @__PURE__ */ jsx(Text, {
25814
+ as: "kbd",
25815
+ variant: "kbd",
25816
+ class: "inline-flex h-4.5 min-w-4.5 items-center justify-center rounded-[5px] bg-current/12 px-1 opacity-80",
25817
+ "data-slot": "kbd",
25818
+ children: key
25819
+ }, index))
25820
+ });
25821
+ }
25822
+ //#endregion
25823
+ //#region ../ui/src/lib/icon-button.tsx
25824
+ /**
25825
+ * Compact control shared by the canvas, sidebar, and embedded product demo.
25826
+ * @param handle - Native button props, icon, label, and active state.
25827
+ * @returns The button render function.
25828
+ */
25829
+ function IconButton(handle) {
25830
+ return () => {
25831
+ let { icon, label, active, surface, size = "default", iconSize = size === "small" ? 14 : 16, tooltip, shortcut, class: className = "", ...props } = handle.props;
25832
+ let button = /* @__PURE__ */ jsx(HeadlessButton, {
25833
+ ...props,
25834
+ "aria-label": label,
25835
+ title: tooltip ? void 0 : props.title ?? label,
25836
+ "aria-pressed": props["aria-pressed"] ?? active,
25837
+ "data-active": active,
25838
+ class: `bud-interaction ${surface ? "bud-control" : ""} flex shrink-0 items-center justify-center ${size === "small" ? "size-6 rounded-md" : "size-control rounded-lg"} ${className}`,
25839
+ children: /* @__PURE__ */ jsx(Icon, {
25840
+ name: icon,
25841
+ size: iconSize
25842
+ })
25843
+ });
25844
+ return tooltip ? /* @__PURE__ */ jsx(Tooltip, {
25845
+ label,
25846
+ shortcut,
25847
+ children: button
25848
+ }) : button;
25849
+ };
25850
+ }
25851
+ //#endregion
25852
+ //#region ../ui/src/lib/scroll-fade.ts
25853
+ /**
25854
+ * Fades a scroller's edges while it has more content past them, after Divine's scroll-fade
25855
+ * utility. The fade only applies while the content overflows, so a short list keeps crisp edges.
25856
+ * @param axis - The direction the element scrolls.
25857
+ * @returns A mixin for the scrolling element.
25858
+ */
25859
+ function scrollFade(axis = "x") {
25860
+ return ref((node, signal) => {
25861
+ let update = () => {
25862
+ if (axis === "x" ? node.scrollWidth > node.clientWidth + 1 : node.scrollHeight > node.clientHeight + 1) node.dataset.scrollFade = axis;
25863
+ else delete node.dataset.scrollFade;
25864
+ };
25865
+ let resize = new ResizeObserver(update);
25866
+ let mutation = new MutationObserver(update);
25867
+ resize.observe(node);
25868
+ mutation.observe(node, {
25869
+ childList: true,
25870
+ subtree: true,
25871
+ characterData: true
25872
+ });
25873
+ update();
25874
+ signal.addEventListener("abort", () => {
25875
+ resize.disconnect();
25876
+ mutation.disconnect();
25877
+ }, { once: true });
25878
+ });
25879
+ }
25880
+ //#endregion
25881
+ //#region ../ui/src/lib/workspace.tsx
25882
+ /**
25883
+ * A panel's toolbar: content on the left, such as a path, and actions on the right. On narrow
25884
+ * screens the actions scroll, fading at their edges.
25885
+ * @param handle - The start content, the actions, and whether a line separates it from the
25886
+ * panel, which content that fades as it scrolls doesn't need.
25887
+ * @returns The toolbar render function.
25888
+ */
25889
+ function WorkspaceToolbar(handle) {
25890
+ return () => /* @__PURE__ */ jsxs("div", {
25891
+ class: `bud-toolbar flex h-12 shrink-0 items-center gap-2 ps-2 pe-2 ${handle.props.divider === false ? "" : "border-b border-line"}`,
25892
+ children: [handle.props.start, /* @__PURE__ */ jsx(ToolbarGroup, {
25893
+ class: "ms-auto min-w-0 [scrollbar-width:none] max-md:overflow-x-auto max-md:p-1",
25894
+ mix: scrollFade("x"),
25895
+ children: handle.props.children
25896
+ })]
25897
+ });
25898
+ }
25899
+ /**
25900
+ * A row of toolbar buttons and separators, spaced alike in every toolbar.
25901
+ * @param handle - Native div props and the buttons.
25902
+ * @returns The group render function.
25903
+ */
25904
+ function ToolbarGroup(handle) {
25905
+ return () => {
25906
+ let { class: className = "", children, ...props } = handle.props;
25907
+ return /* @__PURE__ */ jsx("div", {
25908
+ ...props,
25909
+ class: `flex items-center gap-1.5 ${className}`,
25910
+ children
25911
+ });
25912
+ };
25913
+ }
25914
+ //#endregion
25915
+ //#region ../ui/src/lib/toast-store.ts
25916
+ var defaultDuration = 4e3;
25917
+ var items = [];
25918
+ var listeners = /* @__PURE__ */ new Set();
25919
+ var next = 0;
25920
+ function publish() {
25921
+ for (let listener of listeners) listener(items);
25922
+ }
25923
+ /**
25924
+ * Shows a notification; the newest is in front.
25925
+ * @param title - The message.
25926
+ * @param options - A description, type, duration or stable id.
25927
+ * @returns The toast's id, for dismissing it early.
25928
+ */
25929
+ function toast(title, options = {}) {
25930
+ let id = options.id ?? `toast-${++next}`;
25931
+ items = [{
25932
+ id,
25933
+ title,
25934
+ description: options.description,
25935
+ type: options.type ?? "default",
25936
+ duration: options.duration ?? defaultDuration,
25937
+ removed: false
25938
+ }, ...items.filter((existing) => existing.id !== id)];
25939
+ publish();
25940
+ return id;
25941
+ }
25942
+ toast.success = (title, options = {}) => toast(title, {
25943
+ ...options,
25944
+ type: "success"
25945
+ });
25946
+ toast.error = (title, options = {}) => toast(title, {
25947
+ ...options,
25948
+ type: "error"
25949
+ });
25950
+ /**
25951
+ * Starts a toast's exit; the Toaster removes it once it has animated out.
25952
+ * @param id - The toast to dismiss; every toast when omitted.
25953
+ */
25954
+ toast.dismiss = (id) => {
25955
+ items = items.map((item) => id === void 0 || item.id === id ? {
25956
+ ...item,
25957
+ removed: true
25958
+ } : item);
25959
+ publish();
25960
+ };
25961
+ //#endregion
25922
25962
  //#region ../ui/src/lib/tasks/task-draft.ts
25923
25963
  function editValue(value, field) {
25924
25964
  return field.kind === "json" ? JSON.stringify(value ?? {}, null, 2) : taskValueText(value);