@bud.sh/app 0.4.0 → 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-C8hyPWoT.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-Dpxjiok1.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-DzXdaT9L.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-CHGG-6fo.js → project-threads-Lay351p4.js} +105 -105
  15. package/dist/client/assets/{project-work-L5fYMSOu.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-DGigNdyK.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-MWtOwaWm.js → canvas-V36732ER.js} +6 -4
  23. package/dist/hosted/assets/{comment-data-DsgJWq4n.js → comment-data-CF2ZV_3k.js} +4162 -3659
  24. package/dist/hosted/assets/{files-Wvo11G2A.js → files-DjSCjfU7.js} +5 -3
  25. package/dist/hosted/assets/{pages-BAmRFVtU.js → pages-BzjzeuPu.js} +3 -3
  26. package/dist/hosted/assets/{project-branches-BNloVosX.js → project-branches-BT97J9i7.js} +1 -1
  27. package/dist/hosted/assets/{project-thread-links-BmY8HQXt.js → project-thread-links-BRLgByEe.js} +2 -2
  28. package/dist/hosted/assets/{screen-QmqmI2Aq.js → screen-CKp2C7kx.js} +68 -137
  29. package/dist/hosted/assets/{tasks-C8iWPrWg.js → tasks-Dj7AyBJB.js} +12 -5
  30. package/dist/hosted/assets/{tasks-request-C89fN1dK.js → tasks-request-DZob623r.js} +1 -1
  31. package/dist/hosted/hosted.js +174 -18
  32. package/dist/node/assets/{src-BdvZ2mYc.js → src-CerrKjID.js} +57 -1
  33. package/dist/node/design.js +969 -809
  34. package/dist/node/server.js +25 -3
  35. package/dist/node-host/assets/{canvas-D_F8SIlm.js → canvas-TRUfxfL5.js} +6 -4
  36. package/dist/node-host/assets/{comment-data-q_8INiZz.js → comment-data-DyIZNpfG.js} +4143 -3640
  37. package/dist/node-host/assets/{files-xazOrNCX.js → files-Dh2TiQZq.js} +5 -3
  38. package/dist/node-host/assets/{pages-D6VkmdfQ.js → pages-BxpjbYhs.js} +3 -3
  39. package/dist/node-host/assets/{project-branches-C26hwmo3.js → project-branches-B7HUGiVK.js} +1 -1
  40. package/dist/node-host/assets/{project-thread-links-BOzjws5W.js → project-thread-links-CauJF0Cz.js} +2 -2
  41. package/dist/node-host/assets/{screen-Dog-YByu.js → screen-CZXWJEds.js} +68 -137
  42. package/dist/node-host/assets/{tasks-A1_bpb3_.js → tasks-Bs1IOpeU.js} +12 -5
  43. package/dist/node-host/assets/{tasks-request-Bie1PpQP.js → tasks-request-ZWmbc8fE.js} +1 -1
  44. package/dist/node-host/node-host.js +174 -18
  45. package/dist/server/assets/{canvas-Dn4RYoU2.js → canvas-Dc1mYCwQ.js} +7 -5
  46. package/dist/server/assets/{comment-data-B0OLHBGp.js → comment-data-CsjukwAW.js} +3911 -3619
  47. package/dist/server/assets/{events-6z5v7Uca.js → events-iHpLGr3k.js} +213 -2
  48. package/dist/server/assets/{files-C5Vilfe4.js → files-E9mSFeoc.js} +6 -4
  49. package/dist/server/assets/{listener-o1STuLju.js → listener-DN5TyOjJ.js} +45 -11
  50. package/dist/server/assets/{pages-BNa0MHEc.js → pages-BBBXyJWV.js} +4 -4
  51. package/dist/server/assets/{project-branches-GtQYyOH5.js → project-branches-B0YIK6Ur.js} +2 -2
  52. package/dist/server/assets/{project-thread-links-Nfz9NIny.js → project-thread-links-CBOpf6zk.js} +3 -3
  53. package/dist/server/assets/{project-work-Cy_BpHwR.js → project-work-qSKFO5yj.js} +3 -3
  54. package/dist/server/assets/{screen-BtcJmZwj.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-B82tHwyQ.js → tasks-CiXodsLN.js} +14 -7
  58. package/dist/server/assets/{tasks-request-BtJBlkxV.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-BfF4_N3e.js +0 -8
  65. package/dist/client/assets/files-D_Tm7gCK.js +0 -1
  66. package/dist/client/assets/i18n-BtfXum8P.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-B-_CKvDS.js +0 -1
  70. package/dist/client/assets/project-comments-CQkB2jbE.js +0 -1
  71. package/dist/client/assets/project-thread-links-BN7zL0AX.js +0 -1
  72. package/dist/client/assets/task-design-DYqcqq1b.js +0 -23
  73. package/dist/client/assets/tasks-A2FKo3_h.js +0 -1
  74. package/dist/client/assets/workspace-CvWmU2RK.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"
@@ -3688,6 +3696,10 @@ var de = {
3688
3696
  text: "Dein Projekt nebeneinander.",
3689
3697
  source: "ae5c6a16"
3690
3698
  },
3699
+ "app.controller.discussion": {
3700
+ text: "Diskussion",
3701
+ source: "52a76f71"
3702
+ },
3691
3703
  "appShell.mainNavigation": {
3692
3704
  text: "Hauptnavigation",
3693
3705
  source: "c5a8374a"
@@ -4049,6 +4061,30 @@ var de = {
4049
4061
  text: "Kommentar",
4050
4062
  source: "984f9ffe"
4051
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
+ },
4052
4088
  "comments.commentThread.leaveCommentPlaceholder": {
4053
4089
  text: "Kommentar schreiben…",
4054
4090
  source: "98a1ef74"
@@ -4310,10 +4346,6 @@ var de = {
4310
4346
  text: "Eine Diskussion, gemeinsam für mehrere Aufgaben.",
4311
4347
  source: "f056484b"
4312
4348
  },
4313
- "counter.count": {
4314
- text: "Anzahl:",
4315
- source: "4dc7b9ea"
4316
- },
4317
4349
  "files.codeCommentAnnotation.moved": {
4318
4350
  text: "Verschoben",
4319
4351
  source: "82869bf0"
@@ -5792,6 +5824,10 @@ var de = {
5792
5824
  source: "756213ca"
5793
5825
  },
5794
5826
  "files.fileReviewPanel.selection": {
5827
+ text: "{path} · {start}–{end}",
5828
+ source: "976a3281"
5829
+ },
5830
+ "files.fileReviewPanel.selectionOnSide": {
5795
5831
  text: "{path} · {side} {start}–{end}",
5796
5832
  source: "6445c0da"
5797
5833
  },
@@ -5803,6 +5839,14 @@ var de = {
5803
5839
  text: "{path} · {side} {start}–{end} (Ende {endSide})",
5804
5840
  source: "4d409456"
5805
5841
  },
5842
+ "files.fileReviewPanel.sideNew": {
5843
+ text: "neu",
5844
+ source: "28999611"
5845
+ },
5846
+ "files.fileReviewPanel.sideOld": {
5847
+ text: "alt",
5848
+ source: "bd2b9bd6"
5849
+ },
5806
5850
  "files.fileReviewPanel.sourceFile": {
5807
5851
  text: "Quelldatei",
5808
5852
  source: "f52d16de"
@@ -6666,6 +6710,22 @@ var de = {
6666
6710
  text: "Neu laden",
6667
6711
  source: "1a43a2ac"
6668
6712
  },
6713
+ "screens.canvas": {
6714
+ text: "Leinwand",
6715
+ source: "b2585091"
6716
+ },
6717
+ "screens.files": {
6718
+ text: "Dateien",
6719
+ source: "e71d1b30"
6720
+ },
6721
+ "screens.pages": {
6722
+ text: "Seiten",
6723
+ source: "7decab7f"
6724
+ },
6725
+ "screens.tasks": {
6726
+ text: "Aufgaben",
6727
+ source: "399e986d"
6728
+ },
6669
6729
  "select.noMatches": {
6670
6730
  text: "Keine Treffer",
6671
6731
  source: "d29421ef"
@@ -6863,6 +6923,66 @@ var de = {
6863
6923
  text: "Hoch",
6864
6924
  source: "be0a061d"
6865
6925
  },
6926
+ "tasks.taskData.label.all": {
6927
+ text: "Alle",
6928
+ source: "75afe364"
6929
+ },
6930
+ "tasks.taskData.label.blocked": {
6931
+ text: "Blockiert",
6932
+ source: "e378ac53"
6933
+ },
6934
+ "tasks.taskData.label.bug": {
6935
+ text: "Fehler",
6936
+ source: "d8da8e15"
6937
+ },
6938
+ "tasks.taskData.label.chore": {
6939
+ text: "Routineaufgabe",
6940
+ source: "0f37a562"
6941
+ },
6942
+ "tasks.taskData.label.closed": {
6943
+ text: "Geschlossen",
6944
+ source: "3201cae5"
6945
+ },
6946
+ "tasks.taskData.label.decision": {
6947
+ text: "Entscheidung",
6948
+ source: "be93ff03"
6949
+ },
6950
+ "tasks.taskData.label.deferred": {
6951
+ text: "Zurückgestellt",
6952
+ source: "59a18aca"
6953
+ },
6954
+ "tasks.taskData.label.epic": {
6955
+ text: "Epic",
6956
+ source: "e75e9488"
6957
+ },
6958
+ "tasks.taskData.label.feature": {
6959
+ text: "Funktion",
6960
+ source: "fe9102dd"
6961
+ },
6962
+ "tasks.taskData.label.hooked": {
6963
+ text: "Verknüpft",
6964
+ source: "79750f9b"
6965
+ },
6966
+ "tasks.taskData.label.in_progress": {
6967
+ text: "In Bearbeitung",
6968
+ source: "2948b425"
6969
+ },
6970
+ "tasks.taskData.label.open": {
6971
+ text: "Offen",
6972
+ source: "538b10e9"
6973
+ },
6974
+ "tasks.taskData.label.pinned": {
6975
+ text: "Angeheftet",
6976
+ source: "6d0ba1af"
6977
+ },
6978
+ "tasks.taskData.label.ready": {
6979
+ text: "Bereit",
6980
+ source: "0bca3294"
6981
+ },
6982
+ "tasks.taskData.label.task": {
6983
+ text: "Aufgabe",
6984
+ source: "76ef3d8c"
6985
+ },
6866
6986
  "tasks.taskData.low": {
6867
6987
  text: "Niedrig",
6868
6988
  source: "b1dd9521"
@@ -7663,11 +7783,13 @@ var en = {
7663
7783
  "app.client.projectComments.adapterDoesNotSupportOperation": "The comment adapter does not support this operation.",
7664
7784
  "app.client.projectComments.adapterUnavailable": "The comment adapter is unavailable.",
7665
7785
  "app.client.projectComments.budUndidCommentChange": "Bud undid a comment change",
7786
+ "app.client.projectComments.commentDeleted": "Comment deleted",
7666
7787
  "app.client.projectComments.commentSaved": "Comment saved",
7667
7788
  "app.client.projectComments.commentWriteNotConfirmed": "The comment write was not confirmed.",
7668
7789
  "app.client.projectComments.commentsSynced": "Comments synced",
7669
7790
  "app.client.projectComments.couldNotLoadComments": "Could not load comments.",
7670
7791
  "app.client.projectComments.couldNotLoadDiscussion": "Could not load the discussion.",
7792
+ "app.client.projectComments.discussionDeleted": "Discussion deleted",
7671
7793
  "app.client.projectComments.discussionNoLongerInView": "This discussion is no longer part of this view.",
7672
7794
  "app.client.projectComments.loadCommentsBeforeSaving": "Load comments before saving.",
7673
7795
  "app.client.projectComments.refreshBeforeTryingAgain": "The comment write could not be confirmed. Refresh before trying again.",
@@ -7782,6 +7904,7 @@ var en = {
7782
7904
  "app.client.workspace.retry": "Retry",
7783
7905
  "app.client.workspace.startProjectWithBudVitePlugin": "Start your project with the Bud Vite plugin, or run Bud from its directory. Its screens appear here when the companion connects.",
7784
7906
  "app.client.workspace.yourProjectSideBySide": "Your project, side by side.",
7907
+ "app.controller.discussion": "Discussion",
7785
7908
  "appShell.mainNavigation": "Main navigation",
7786
7909
  "breadcrumb.navigationLabel": "Breadcrumb",
7787
7910
  "canvas.artboardCanvas.chooseCollectionPage": "Choose a {collection} page",
@@ -7888,6 +8011,12 @@ var en = {
7888
8011
  "comments.commentPins.detachedTitle": "{name} · Its element is no longer on the page",
7889
8012
  "comments.commentThread.cancel": "Cancel",
7890
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",
7891
8020
  "comments.commentThread.leaveCommentPlaceholder": "Leave a comment…",
7892
8021
  "comments.commentThread.loadingDiscussion": "Loading discussion…",
7893
8022
  "comments.commentThread.openDiscussion": "Open discussion",
@@ -7960,7 +8089,6 @@ var en = {
7960
8089
  other: "{count} selected"
7961
8090
  },
7962
8091
  "comments.threadTaskLinks.sharedDiscussionSubtitle": "One discussion, shared across tasks.",
7963
- "counter.count": "Count:",
7964
8092
  "files.codeCommentAnnotation.moved": "Moved",
7965
8093
  "files.codeCommentAnnotation.openDiscussion": "Open code discussion: {body}",
7966
8094
  "files.editorCommands.addNextMatchingSelection": "Add next matching selection",
@@ -8373,9 +8501,12 @@ var en = {
8373
8501
  "files.fileReviewPanel.reviewUndo": "Review undo",
8374
8502
  "files.fileReviewPanel.searchContents": "Search contents",
8375
8503
  "files.fileReviewPanel.selectedFile": "Selected file",
8376
- "files.fileReviewPanel.selection": "{path} · {side} {start}–{end}",
8504
+ "files.fileReviewPanel.selection": "{path} · {start}–{end}",
8505
+ "files.fileReviewPanel.selectionOnSide": "{path} · {side} {start}–{end}",
8377
8506
  "files.fileReviewPanel.selectionStatus": " · {status}",
8378
8507
  "files.fileReviewPanel.selectionWithEndSide": "{path} · {side} {start}–{end} ({endSide} end)",
8508
+ "files.fileReviewPanel.sideNew": "new",
8509
+ "files.fileReviewPanel.sideOld": "old",
8379
8510
  "files.fileReviewPanel.sourceFile": "Source file",
8380
8511
  "files.fileReviewPanel.stageFile": "Stage file",
8381
8512
  "files.fileReviewPanel.stageSelectedLines": "Stage selected lines",
@@ -8612,6 +8743,10 @@ var en = {
8612
8743
  "pages.pagesWorkspace.pageList": "Page list",
8613
8744
  "pages.pagesWorkspace.pageStatus": "Page status",
8614
8745
  "pages.pagesWorkspace.reload": "Reload",
8746
+ "screens.canvas": "Canvas",
8747
+ "screens.files": "Files",
8748
+ "screens.pages": "Pages",
8749
+ "screens.tasks": "Tasks",
8615
8750
  "select.noMatches": "No matches",
8616
8751
  "select.optionsLabel": "{label} options",
8617
8752
  "select.searchLabel": "Search {label}",
@@ -8668,6 +8803,21 @@ var en = {
8668
8803
  "tasks.taskData.allTasks": "All tasks",
8669
8804
  "tasks.taskData.backlog": "Backlog",
8670
8805
  "tasks.taskData.high": "High",
8806
+ "tasks.taskData.label.all": "All",
8807
+ "tasks.taskData.label.blocked": "Blocked",
8808
+ "tasks.taskData.label.bug": "Bug",
8809
+ "tasks.taskData.label.chore": "Chore",
8810
+ "tasks.taskData.label.closed": "Closed",
8811
+ "tasks.taskData.label.decision": "Decision",
8812
+ "tasks.taskData.label.deferred": "Deferred",
8813
+ "tasks.taskData.label.epic": "Epic",
8814
+ "tasks.taskData.label.feature": "Feature",
8815
+ "tasks.taskData.label.hooked": "Hooked",
8816
+ "tasks.taskData.label.in_progress": "In progress",
8817
+ "tasks.taskData.label.open": "Open",
8818
+ "tasks.taskData.label.pinned": "Pinned",
8819
+ "tasks.taskData.label.ready": "Ready",
8820
+ "tasks.taskData.label.task": "Task",
8671
8821
  "tasks.taskData.low": "Low",
8672
8822
  "tasks.taskData.noEpic": "No epic",
8673
8823
  "tasks.taskData.noStatus": "No status",
@@ -8856,6 +9006,14 @@ var catalogs = {
8856
9006
  };
8857
9007
  var current = "en";
8858
9008
  /**
9009
+ * Whether the English catalog has a message by this name, for text whose key is built from data.
9010
+ * @param key - A possible message key.
9011
+ * @returns Whether `t(key)` finds a message.
9012
+ */
9013
+ function hasMessage(key) {
9014
+ return Object.hasOwn(en, key);
9015
+ }
9016
+ /**
8859
9017
  * The language messages are shown in now.
8860
9018
  * @returns The request's locale on a server, or the page's.
8861
9019
  */
@@ -8993,6 +9151,8 @@ createMatcher(taskRoutes.show.pattern);
8993
9151
  * @returns The label.
8994
9152
  */
8995
9153
  function taskLabel(value) {
9154
+ let key = `tasks.taskData.label.${value}`;
9155
+ if (hasMessage(key)) return t(key);
8996
9156
  return value.replaceAll("_", " ").replaceAll("-", " ").replace(/^./, (letter) => letter.toUpperCase());
8997
9157
  }
8998
9158
  /**
@@ -24072,128 +24232,199 @@ function Breadcrumb(handle) {
24072
24232
  };
24073
24233
  }
24074
24234
  //#endregion
24075
- //#region ../ui/src/lib/headless-button.tsx
24076
- /**
24077
- * A native button with shared interactions; callers own its content and geometry.
24078
- * @param handle - Native button props, content, and Remix mixins.
24079
- * @returns The button render function.
24080
- */
24081
- function HeadlessButton(handle) {
24082
- return () => {
24083
- let { class: className, type = "button", ...props } = handle.props;
24084
- let attributes = ariaAttributes(props);
24085
- return /* @__PURE__ */ jsx("button", {
24086
- ...attributes,
24087
- type,
24088
- class: ["bud-headless-button", className].filter(Boolean).join(" ")
24089
- });
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
24242
+ };
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
24090
24259
  };
24091
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
+ }
24092
24283
  //#endregion
24093
- //#region ../ui/src/lib/comments/comment-data.ts
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);
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
24094
24290
  /**
24095
- * Takes the first letters of a name's first and last words.
24096
- * @param name - The author's name.
24097
- * @returns One or two capital letters, or `?` for an empty name.
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
24098
24296
  */
24099
- function commentInitials(name) {
24100
- let parts = name.trim().split(/\s+/u).filter(Boolean);
24101
- return `${parts[0]?.[0] ?? "?"}${parts.length > 1 ? parts.at(-1)[0] : ""}`.toUpperCase();
24297
+ function invariant(assertion, message) {
24298
+ let prefix = "Framework invariant";
24299
+ if (assertion) return;
24300
+ throw new Error(message ? `${prefix}: ${message}` : prefix);
24301
+ }
24302
+ //#endregion
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;
24102
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
24103
24322
  /**
24104
- * Gives an author a stable color.
24105
- * @param id - The author's id.
24106
- * @returns A hue from 0 to 359.
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.
24107
24327
  */
24108
- function commentAuthorHue(id) {
24109
- let hash = 0;
24110
- for (let character of id) hash = (hash * 31 + character.codePointAt(0)) % 360;
24111
- return hash;
24328
+ function createMixin(type) {
24329
+ return (...args) => ({
24330
+ type,
24331
+ args
24332
+ });
24112
24333
  }
24113
24334
  //#endregion
24114
- //#region ../ui/src/lib/comments/comment-thread.tsx
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
+ };
24369
+ };
24370
+ var onMixin = createMixin(onMixinType);
24115
24371
  /**
24116
- * Shows an author's initials on a circle in their color.
24117
- * @param handle - The author, and whether the avatar is the compact size.
24118
- * @returns The avatar 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.
24119
24378
  */
24120
- function CommentAvatar(handle) {
24121
- return () => {
24122
- let { author, compact = false, class: className = "" } = handle.props;
24123
- return /* @__PURE__ */ jsx(Text, {
24124
- variant: "ui-caption",
24125
- weight: "medium",
24126
- class: `flex shrink-0 items-center justify-center rounded-full text-white ${compact ? "size-5" : "size-6"} ${className}`,
24127
- title: author.name,
24128
- "data-testid": "comment-avatar",
24129
- style: { backgroundColor: `oklch(0.55 0.14 ${commentAuthorHue(author.id)})` },
24130
- children: /* @__PURE__ */ jsx("span", {
24131
- class: compact ? "scale-[0.833]" : void 0,
24132
- children: commentInitials(author.name)
24133
- })
24134
- });
24135
- };
24379
+ function on$1(type, handler, captureBoolean) {
24380
+ return onMixin(type, handler, captureBoolean);
24136
24381
  }
24137
24382
  //#endregion
24138
- //#region ../ui/src/lib/dialog.tsx
24139
- var sizes = {
24140
- default: "max-w-[35rem]",
24141
- large: "max-w-[39rem]",
24142
- wide: "max-w-[51rem]"
24143
- };
24383
+ //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/create-element.js
24144
24384
  /**
24145
- * The one modal: a native dialog on a dimmed, blurred backdrop whose content is a FieldCard,
24146
- * raised above the page. It takes focus when it opens, returns it when it closes, and closes on
24147
- * Escape or a click on the backdrop.
24148
- * @param handle - The label, size, close handler and content.
24149
- * @returns The dialog's render function.
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.
24150
24391
  */
24151
- function Dialog(handle) {
24152
- let pressedOutside = false;
24153
- function close() {
24154
- if (handle.props.dismissible !== false) handle.props.onClose();
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);
24155
24399
  }
24156
- return () => {
24157
- let { label, size = "default", initialFocus, children } = handle.props;
24158
- return /* @__PURE__ */ jsx("dialog", {
24159
- "aria-label": label,
24160
- 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]}`,
24161
- mix: [
24162
- ref((node, signal) => openDialog(node, signal, initialFocus)),
24163
- on("cancel", (event) => {
24164
- event.preventDefault();
24165
- close();
24166
- }),
24167
- on("pointerdown", (event) => {
24168
- pressedOutside = event.target === event.currentTarget;
24169
- }),
24170
- on("click", (event) => {
24171
- if (pressedOutside && event.target === event.currentTarget) close();
24172
- })
24173
- ],
24174
- children
24175
- });
24176
- };
24400
+ if (nextProps.key != null) {
24401
+ let { key, ...rest } = nextProps;
24402
+ return jsx$1(type, rest, key);
24403
+ }
24404
+ return jsx$1(type, nextProps);
24177
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
+ });
24178
24420
  /**
24179
- * Owns a native modal and returns focus once Remix removes its host node.
24180
- * @param node - The mounted dialog.
24181
- * @param signal - The host's lifetime, including its removal.
24182
- * @param initialFocus - An optional selector for the element to focus first.
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.
24183
24425
  */
24184
- function openDialog(node, signal, initialFocus) {
24185
- let document = node.ownerDocument;
24186
- let previous = document.activeElement;
24187
- node.showModal();
24188
- if (initialFocus) queueMicrotask(() => {
24189
- if (!signal.aborted) node.querySelector(initialFocus)?.focus();
24190
- });
24191
- signal.addEventListener("abort", () => {
24192
- node.close();
24193
- queueMicrotask(() => {
24194
- if (previous instanceof HTMLElement && previous.isConnected && [...document.querySelectorAll("dialog:modal")].every((dialog) => dialog.contains(previous))) previous.focus({ preventScroll: true });
24195
- });
24196
- }, { once: true });
24426
+ function attrs(defaults) {
24427
+ return attrsMixin(defaults);
24197
24428
  }
24198
24429
  //#endregion
24199
24430
  //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/anchor.js
@@ -24640,572 +24871,262 @@ function anchor$1(floating, anchorTarget, options = {}) {
24640
24871
  };
24641
24872
  }
24642
24873
  //#endregion
24643
- //#region ../ui/src/lib/shortcut.ts
24644
- var symbols = {
24645
- arrowup: "↑",
24646
- arrowdown: "↓",
24647
- arrowleft: "←",
24648
- arrowright: "→",
24649
- enter: "↵",
24650
- escape: "Esc",
24651
- backspace: "⌫",
24652
- tab: "⇥",
24653
- space: "Space"
24654
- };
24655
- /**
24656
- * Reads a combo such as `mod+shift+k`; the last part is the key, so `mod++` means mod and plus.
24657
- * @param combo - Modifiers and a key joined by `+`.
24658
- * @returns The key and its modifiers.
24659
- */
24660
- function parseShortcut(combo) {
24661
- let separator = combo.length < 2 ? -1 : combo.lastIndexOf("+", combo.length - 2);
24662
- let modifiers = separator < 0 ? [] : combo.slice(0, separator).toLowerCase().split("+");
24663
- return {
24664
- key: combo.slice(separator + 1),
24665
- mod: modifiers.includes("mod"),
24666
- shift: modifiers.includes("shift"),
24667
- 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;
24668
24897
  };
24669
- }
24670
- /**
24671
- * The keycaps to show for a combo, in the platform's order and notation.
24672
- * @param combo - Modifiers and a key joined by `+`.
24673
- * @param apple - Whether to use Apple symbols; detected from the browser by default.
24674
- * @returns One label per key.
24675
- */
24676
- function shortcutKeys(combo, apple = isApple()) {
24677
- let { key, mod, shift, alt } = parseShortcut(combo);
24678
- let label = symbols[key.toLowerCase()] ?? (key.length === 1 ? key.toUpperCase() : key);
24679
- if (apple) return [
24680
- alt && "⌥",
24681
- shift && "⇧",
24682
- mod && "⌘",
24683
- label
24684
- ].filter((part) => part !== false);
24685
- return [
24686
- mod && "Ctrl",
24687
- alt && "Alt",
24688
- shift && "Shift",
24689
- label
24690
- ].filter((part) => part !== false);
24691
- }
24692
- function isApple() {
24693
- return typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.platform);
24694
- }
24898
+ });
24695
24899
  //#endregion
24696
- //#region ../ui/src/lib/tooltip.tsx
24697
- var delay = 400;
24698
- var warmFor = 300;
24699
- var closedAt = 0;
24700
- /**
24701
- * Names a control on hover or keyboard focus, with its shortcut. The control keeps its own
24702
- * accessible name; the tooltip describes it.
24703
- * @param handle - The label, optional shortcut and the control it describes.
24704
- * @returns The tooltip render function.
24705
- */
24706
- function Tooltip(handle) {
24707
- let open = false;
24708
- let timer;
24709
- let trigger;
24710
- let release = () => {};
24711
- let id = `${handle.id}-tooltip`;
24712
- function show(immediately) {
24713
- clearTimeout(timer);
24714
- if (open) return;
24715
- let reveal = () => {
24716
- open = true;
24717
- 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
24718
24919
  };
24719
- if (immediately || Date.now() - closedAt < warmFor) reveal();
24720
- else timer = setTimeout(reveal, delay);
24721
- }
24722
- function hide() {
24723
- clearTimeout(timer);
24724
- if (!open) return;
24725
- open = false;
24726
- closedAt = Date.now();
24727
- handle.update();
24920
+ documentElement.style.overflow = "hidden";
24921
+ if (scrollbarWidth > 0 && computedScrollbarGutter === "auto") documentElement.style.scrollbarGutter = "stable";
24922
+ scrollLocks.set(document, state);
24728
24923
  }
24729
- return () => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
24730
- class: "inline-flex",
24731
- mix: [
24732
- ref((node, signal) => {
24733
- trigger = node.firstElementChild instanceof HTMLElement ? node.firstElementChild : node;
24734
- trigger.setAttribute("aria-describedby", id);
24735
- let escape = (event) => {
24736
- if (event.key === "Escape") hide();
24737
- };
24738
- document.addEventListener("keydown", escape, { signal });
24739
- signal.addEventListener("abort", () => clearTimeout(timer), { once: true });
24740
- }),
24741
- on("pointerenter", (event) => {
24742
- if (event.pointerType === "mouse") show(false);
24743
- }),
24744
- on("pointerleave", hide),
24745
- on("pointerdown", hide),
24746
- on("focusin", (event) => {
24747
- if (event.target.dataset.focusMethod === "keyboard") show(true);
24748
- }),
24749
- on("focusout", hide)
24750
- ],
24751
- children: handle.props.children
24752
- }), /* @__PURE__ */ jsxs("div", {
24753
- id,
24754
- role: "tooltip",
24755
- popover: "manual",
24756
- 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",
24757
- mix: ref((node, signal) => {
24758
- if (!open || !trigger) return;
24759
- node.showPopover();
24760
- release = anchor$1(node, trigger, {
24761
- placement: handle.props.placement ?? "top",
24762
- offset: 6
24763
- });
24764
- signal.addEventListener("abort", () => {
24765
- release();
24766
- if (node.isConnected && node.matches(":popover-open")) node.hidePopover();
24767
- }, { once: true });
24768
- }),
24769
- hidden: !open,
24770
- children: [/* @__PURE__ */ jsx(Text, {
24771
- variant: "ui-caption",
24772
- weight: "medium",
24773
- children: handle.props.label
24774
- }), handle.props.shortcut ? /* @__PURE__ */ jsx(Kbd, { shortcut: handle.props.shortcut }) : null]
24775
- }, open ? "open" : "closed")] });
24776
- }
24777
- /**
24778
- * A shortcut as keycaps, such as ⌘ K, in the platform's notation.
24779
- * @param handle - A combo such as `mod+shift+k`.
24780
- * @returns The keycaps render function.
24781
- */
24782
- function Kbd(handle) {
24783
- return () => /* @__PURE__ */ jsx("span", {
24784
- class: `inline-flex items-center gap-0.5 ${handle.props.class ?? ""}`,
24785
- children: shortcutKeys(handle.props.shortcut).map((key, index) => /* @__PURE__ */ jsx(Text, {
24786
- as: "kbd",
24787
- variant: "kbd",
24788
- class: "inline-flex h-4.5 min-w-4.5 items-center justify-center rounded-[5px] bg-current/12 px-1 opacity-80",
24789
- "data-slot": "kbd",
24790
- children: key
24791
- }, index))
24792
- });
24793
- }
24794
- //#endregion
24795
- //#region ../ui/src/lib/icon-button.tsx
24796
- /**
24797
- * Compact control shared by the canvas, sidebar, and embedded product demo.
24798
- * @param handle - Native button props, icon, label, and active state.
24799
- * @returns The button render function.
24800
- */
24801
- function IconButton(handle) {
24924
+ state.count++;
24925
+ let unlocked = false;
24802
24926
  return () => {
24803
- let { icon, label, active, surface, size = "default", iconSize = size === "small" ? 14 : 16, tooltip, shortcut, class: className = "", ...props } = handle.props;
24804
- let button = /* @__PURE__ */ jsx(HeadlessButton, {
24805
- ...props,
24806
- "aria-label": label,
24807
- title: tooltip ? void 0 : props.title ?? label,
24808
- "aria-pressed": props["aria-pressed"] ?? active,
24809
- "data-active": active,
24810
- class: `bud-interaction ${surface ? "bud-control" : ""} flex shrink-0 items-center justify-center ${size === "small" ? "size-6 rounded-md" : "size-control rounded-lg"} ${className}`,
24811
- children: /* @__PURE__ */ jsx(Icon, {
24812
- name: icon,
24813
- size: iconSize
24814
- })
24815
- });
24816
- return tooltip ? /* @__PURE__ */ jsx(Tooltip, {
24817
- label,
24818
- shortcut,
24819
- children: button
24820
- }) : button;
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);
24821
24937
  };
24822
24938
  }
24823
24939
  //#endregion
24824
- //#region ../ui/src/lib/scroll-fade.ts
24825
- /**
24826
- * Fades a scroller's edges while it has more content past them, after Divine's scroll-fade
24827
- * utility. The fade only applies while the content overflows, so a short list keeps crisp edges.
24828
- * @param axis - The direction the element scrolls.
24829
- * @returns A mixin for the scrolling element.
24830
- */
24831
- function scrollFade(axis = "x") {
24832
- return ref((node, signal) => {
24833
- let update = () => {
24834
- if (axis === "x" ? node.scrollWidth > node.clientWidth + 1 : node.scrollHeight > node.clientHeight + 1) node.dataset.scrollFade = axis;
24835
- else delete node.dataset.scrollFade;
24836
- };
24837
- let resize = new ResizeObserver(update);
24838
- let mutation = new MutationObserver(update);
24839
- resize.observe(node);
24840
- mutation.observe(node, {
24841
- childList: true,
24842
- subtree: true,
24843
- characterData: true
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
24947
+ });
24948
+ return () => handle.props.children;
24949
+ }
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
+ };
24844
24958
  });
24845
- update();
24959
+ };
24960
+ });
24961
+ function anchorContains(anchor, target) {
24962
+ return anchor?.target instanceof HTMLElement && anchor.target.contains(target);
24963
+ }
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;
24846
24971
  signal.addEventListener("abort", () => {
24847
- resize.disconnect();
24848
- mutation.disconnect();
24849
- }, { once: true });
24972
+ if (context.surface === node) context.surface = null;
24973
+ });
24850
24974
  });
24851
- }
24852
- //#endregion
24853
- //#region ../ui/src/lib/workspace.tsx
24854
- /**
24855
- * A panel's toolbar: content on the left, such as a path, and actions on the right. On narrow
24856
- * screens the actions scroll, fading at their edges.
24857
- * @param handle - The start content, the actions, and whether a line separates it from the
24858
- * panel, which content that fades as it scrolls doesn't need.
24859
- * @returns The toolbar render function.
24860
- */
24861
- function WorkspaceToolbar(handle) {
24862
- return () => /* @__PURE__ */ jsxs("div", {
24863
- class: `bud-toolbar flex h-12 shrink-0 items-center gap-2 ps-2 pe-2 ${handle.props.divider === false ? "" : "border-b border-line"}`,
24864
- children: [handle.props.start, /* @__PURE__ */ jsx(ToolbarGroup, {
24865
- class: "ms-auto min-w-0 [scrollbar-width:none] max-md:overflow-x-auto max-md:p-1",
24866
- mix: scrollFade("x"),
24867
- children: handle.props.children
24868
- })]
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;
24869
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;
24870
25076
  }
25077
+ //#endregion
25078
+ //#region ../ui/src/lib/headless-button.tsx
24871
25079
  /**
24872
- * A row of toolbar buttons and separators, spaced alike in every toolbar.
24873
- * @param handle - Native div props and the buttons.
24874
- * @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.
24875
25083
  */
24876
- function ToolbarGroup(handle) {
25084
+ function HeadlessButton(handle) {
24877
25085
  return () => {
24878
- let { class: className = "", children, ...props } = handle.props;
24879
- return /* @__PURE__ */ jsx("div", {
24880
- ...props,
24881
- class: `flex items-center gap-1.5 ${className}`,
24882
- 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(" ")
24883
25092
  });
24884
25093
  };
24885
25094
  }
24886
25095
  //#endregion
24887
- //#region ../ui/src/lib/toast-store.ts
24888
- var defaultDuration = 4e3;
24889
- var items = [];
24890
- var listeners = /* @__PURE__ */ new Set();
24891
- var next = 0;
24892
- function publish() {
24893
- 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
+ });
24894
25101
  }
24895
- /**
24896
- * Shows a notification; the newest is in front.
24897
- * @param title - The message.
24898
- * @param options - A description, type, duration or stable id.
24899
- * @returns The toast's id, for dismissing it early.
24900
- */
24901
- function toast(title, options = {}) {
24902
- let id = options.id ?? `toast-${++next}`;
24903
- items = [{
24904
- id,
24905
- title,
24906
- description: options.description,
24907
- type: options.type ?? "default",
24908
- duration: options.duration ?? defaultDuration,
24909
- removed: false
24910
- }, ...items.filter((existing) => existing.id !== id)];
24911
- publish();
24912
- return id;
25102
+ async function flashAttribute(node, attributeName, duration) {
25103
+ node.setAttribute(attributeName, "true");
25104
+ await wait(duration);
25105
+ node.removeAttribute(attributeName);
24913
25106
  }
24914
- toast.success = (title, options = {}) => toast(title, {
24915
- ...options,
24916
- type: "success"
24917
- });
24918
- toast.error = (title, options = {}) => toast(title, {
24919
- ...options,
24920
- type: "error"
24921
- });
24922
- /**
24923
- * Starts a toast's exit; the Toaster removes it once it has animated out.
24924
- * @param id - The toast to dismiss; every toast when omitted.
24925
- */
24926
- toast.dismiss = (id) => {
24927
- items = items.map((item) => id === void 0 || item.id === id ? {
24928
- ...item,
24929
- removed: true
24930
- } : item);
24931
- publish();
24932
- };
24933
25107
  //#endregion
24934
- //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/core/vnode.js
24935
- function createRemixElement(type, props, key) {
24936
- return {
24937
- $rmx: true,
24938
- key,
24939
- props: normalizeElementProps(props),
24940
- type
24941
- };
24942
- }
24943
- function isRemixElement(node) {
24944
- if (typeof node !== "object" || node === null) return false;
24945
- let type = Reflect.get(node, "type");
24946
- let props = Reflect.get(node, "props");
24947
- return Reflect.get(node, "$rmx") === true && (typeof type === "string" || typeof type === "function") && typeof props === "object" && props !== null;
24948
- }
24949
- function normalizeElementProps(props) {
24950
- if (!props) return {};
24951
- if (!("mix" in props)) return props;
24952
- if (isNormalizedMix(props.mix)) return props;
24953
- let { mix, ...rest } = props;
24954
- let normalizedMix = normalizeMixValue(mix);
24955
- return normalizedMix === void 0 ? rest : {
24956
- ...rest,
24957
- mix: normalizedMix
24958
- };
24959
- }
24960
- function isNormalizedMix(mix) {
24961
- if (!Array.isArray(mix) || mix.length === 0) return false;
24962
- for (let i = 0; i < mix.length; i++) {
24963
- let item = mix[i];
24964
- if (!item || Array.isArray(item)) return false;
25108
+ //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/listbox.js
25109
+ function ListboxProvider(handle) {
25110
+ let options = [];
25111
+ let state = "idle";
25112
+ function getOption(value) {
25113
+ return options.find((option) => option.value === value);
24965
25114
  }
24966
- return true;
24967
- }
24968
- function normalizeMixValue(mix) {
24969
- if (!mix) return void 0;
24970
- let normalizedMix = [];
24971
- flattenMixValue(mix, normalizedMix);
24972
- return normalizedMix.length === 0 ? void 0 : normalizedMix;
24973
- }
24974
- function flattenMixValue(mix, out) {
24975
- if (!mix) return;
24976
- if (!Array.isArray(mix)) {
24977
- out.push(mix);
24978
- return;
25115
+ function isVisibleOption(option) {
25116
+ return !!option?.node?.isConnected && !option.hidden;
24979
25117
  }
24980
- for (let item of mix) flattenMixValue(item, out);
24981
- }
24982
- //#endregion
24983
- //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/jsx.js
24984
- function jsx$1(type, props, key) {
24985
- return createRemixElement(type, props, key);
24986
- }
24987
- //#endregion
24988
- //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/invariant.js
24989
- /**
24990
- * Use this when framework code is incorrect, indicating an internal bug rather
24991
- * than application code error.
24992
- *
24993
- * @param assertion The value to check for truthiness
24994
- * @param message Optional message to include in the error
24995
- */
24996
- function invariant(assertion, message) {
24997
- let prefix = "Framework invariant";
24998
- if (assertion) return;
24999
- throw new Error(message ? `${prefix}: ${message}` : prefix);
25000
- }
25001
- //#endregion
25002
- //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/core/children.js
25003
- function normalizeChildren(children) {
25004
- for (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return children.flat(Infinity);
25005
- return children;
25006
- }
25007
- function assertRemixNodes(values) {
25008
- for (let value of values) invariant(isRemixNode(value), "Invalid child node");
25009
- }
25010
- function isRemixNode(value) {
25011
- if (value == null) return true;
25012
- let type = typeof value;
25013
- if (type === "string" || type === "number" || type === "bigint" || type === "boolean") return true;
25014
- if (isRemixElement(value)) return true;
25015
- if (!Array.isArray(value)) return false;
25016
- for (let child of value) if (!isRemixNode(child)) return false;
25017
- return true;
25018
- }
25019
- //#endregion
25020
- //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/mixins/mixin.js
25021
- /**
25022
- * Creates a typed mixin factory that can be passed through the `mix` prop.
25023
- *
25024
- * @param type Mixin setup function.
25025
- * @returns A function that captures mixin arguments and returns a descriptor.
25026
- */
25027
- function createMixin(type) {
25028
- return (...args) => ({
25029
- type,
25030
- args
25031
- });
25032
- }
25033
- //#endregion
25034
- //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/mixins/on-mixin.js
25035
- var onMixinType = (handle) => {
25036
- let currentHandler = () => {};
25037
- let currentType = "";
25038
- let currentCapture = false;
25039
- let currentNode = null;
25040
- let reentry = null;
25041
- let stableHandler = (event) => {
25042
- reentry?.abort(new DOMException("", "EventReentry"));
25043
- reentry = new AbortController();
25044
- currentHandler(event, reentry.signal);
25045
- };
25046
- handle.addEventListener("insert", (event) => {
25047
- currentNode = event.node;
25048
- currentNode.addEventListener(currentType, stableHandler, currentCapture);
25049
- });
25050
- handle.addEventListener("remove", () => {
25051
- currentNode?.removeEventListener(currentType, stableHandler, currentCapture);
25052
- currentNode = null;
25053
- reentry?.abort(new DOMException("", "AbortError"));
25054
- });
25055
- return (type, handler, captureBoolean = false) => {
25056
- let previousType = currentType;
25057
- let previousCapture = currentCapture;
25058
- let needsRebind = currentType !== type || currentCapture !== captureBoolean;
25059
- currentType = type;
25060
- currentHandler = handler;
25061
- currentCapture = captureBoolean;
25062
- if (needsRebind && currentNode) {
25063
- currentNode.removeEventListener(previousType, stableHandler, previousCapture);
25064
- currentNode.addEventListener(type, stableHandler, captureBoolean);
25065
- }
25066
- return handle.element;
25067
- };
25068
- };
25069
- var onMixin = createMixin(onMixinType);
25070
- /**
25071
- * Attaches a typed DOM event handler through the mixin system.
25072
- *
25073
- * @param type Event type to listen for.
25074
- * @param handler Event handler.
25075
- * @param captureBoolean Whether to listen during capture.
25076
- * @returns A mixin descriptor for the target element.
25077
- */
25078
- function on$1(type, handler, captureBoolean) {
25079
- return onMixin(type, handler, captureBoolean);
25080
- }
25081
- //#endregion
25082
- //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/create-element.js
25083
- /**
25084
- * Creates a Remix virtual element from a JSX-like call signature.
25085
- *
25086
- * @param type Host tag, component function, or mixin host placeholder.
25087
- * @param props Element props.
25088
- * @param children Child nodes.
25089
- * @returns A Remix virtual element.
25090
- */
25091
- function createElement$1(type, props, ...children) {
25092
- let nextProps = { ...props ?? {} };
25093
- if (isMixinElementType(type)) {
25094
- if (children.length > 0) console.error(/* @__PURE__ */ new Error("mixin elements must not receive children"));
25095
- } else {
25096
- assertRemixNodes(children);
25097
- nextProps.children = normalizeChildren(children);
25118
+ function isInteractableOption(option) {
25119
+ return isVisibleOption(option) && !option?.disabled;
25098
25120
  }
25099
- if (nextProps.key != null) {
25100
- let { key, ...rest } = nextProps;
25101
- return jsx$1(type, rest, key);
25121
+ function getInteractableOptions() {
25122
+ return options.filter(isInteractableOption);
25102
25123
  }
25103
- return jsx$1(type, nextProps);
25104
- }
25105
- function isMixinElementType(type) {
25106
- return typeof type === "function" && "__rmxMixinElementType" in type;
25107
- }
25108
- //#endregion
25109
- //#region ../../node_modules/.pnpm/@remix-run+component@1.0.0_patch_hash=ed95ce6ac654c31698f27fd52f980c5f1d3e725ba0116b6d666a9b6b3a67016b/node_modules/@remix-run/component/dist/runtime/mixins/attrs-mixin.js
25110
- var attrsMixin = createMixin((handle) => (defaults, props) => {
25111
- let nextProps = props;
25112
- for (let key in defaults) {
25113
- if (props[key] !== void 0) continue;
25114
- if (nextProps === props) nextProps = { ...props };
25115
- nextProps[key] = defaults[key];
25116
- }
25117
- return nextProps === props ? handle.element : createElement$1(handle.element, nextProps);
25118
- });
25119
- /**
25120
- * Applies default host props unless the element already provides them explicitly.
25121
- *
25122
- * @param defaults Default props to apply when the element does not already define them.
25123
- * @returns A mixin descriptor that provides default host props.
25124
- */
25125
- function attrs(defaults) {
25126
- return attrsMixin(defaults);
25127
- }
25128
- //#endregion
25129
- //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/lib/typeahead.js
25130
- var HIDDEN_TYPEAHEAD_TIMEOUT = 750;
25131
- var hiddenTypeahead = createMixin((handle) => {
25132
- let text = "";
25133
- let timeoutId = 0;
25134
- function clearTypeahead() {
25135
- clearTimeout(timeoutId);
25136
- timeoutId = 0;
25137
- text = "";
25138
- }
25139
- function updateTypeahead(nextText, onTypeahead) {
25140
- text = nextText;
25141
- onTypeahead(text);
25142
- }
25143
- handle.addEventListener("remove", clearTypeahead);
25144
- return (onTypeahead) => [on$1("focusout", (event) => {
25145
- if (event.relatedTarget instanceof Node && event.currentTarget.contains(event.relatedTarget)) return;
25146
- clearTypeahead();
25147
- }), on$1("keydown", (event) => {
25148
- if (event.key.length === 1 && !event.ctrlKey && !event.altKey && !event.metaKey) {
25149
- clearTimeout(timeoutId);
25150
- updateTypeahead(text + event.key.toLowerCase(), onTypeahead);
25151
- timeoutId = window.setTimeout(clearTypeahead, HIDDEN_TYPEAHEAD_TIMEOUT);
25152
- } else if (event.key === "Escape") clearTypeahead();
25153
- else if (event.key === "Backspace" && text.length > 0) {
25154
- clearTimeout(timeoutId);
25155
- updateTypeahead(text.slice(0, -1), onTypeahead);
25156
- timeoutId = window.setTimeout(clearTypeahead, HIDDEN_TYPEAHEAD_TIMEOUT);
25157
- }
25158
- })];
25159
- });
25160
- function itemMatchesSearchText(item, text, getSearchValues) {
25161
- let values = getSearchValues(item);
25162
- let searchValues = Array.isArray(values) ? values : [values];
25163
- let normalizedText = text.toLowerCase();
25164
- return searchValues.some((value) => value.toLowerCase().startsWith(normalizedText));
25165
- }
25166
- function matchNextItemBySearchText(text, items, options) {
25167
- if (text === "") return null;
25168
- for (let offset = 1; offset <= items.length; offset++) {
25169
- let item = items[(options.fromIndex + offset + items.length) % items.length];
25170
- if (itemMatchesSearchText(item, text, options.getSearchValues)) return item;
25171
- }
25172
- return null;
25173
- }
25174
- //#endregion
25175
- //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/lib/flash-attribute.js
25176
- function wait(ms) {
25177
- return new Promise((resolve) => {
25178
- setTimeout(resolve, ms);
25179
- });
25180
- }
25181
- async function flashAttribute(node, attributeName, duration) {
25182
- node.setAttribute(attributeName, "true");
25183
- await wait(duration);
25184
- node.removeAttribute(attributeName);
25185
- }
25186
- //#endregion
25187
- //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/listbox.js
25188
- function ListboxProvider(handle) {
25189
- let options = [];
25190
- let state = "idle";
25191
- function getOption(value) {
25192
- return options.find((option) => option.value === value);
25193
- }
25194
- function isVisibleOption(option) {
25195
- return !!option?.node?.isConnected && !option.hidden;
25196
- }
25197
- function isInteractableOption(option) {
25198
- return isVisibleOption(option) && !option?.disabled;
25199
- }
25200
- function getInteractableOptions() {
25201
- return options.filter(isInteractableOption);
25202
- }
25203
- function scrollOptionIntoView(option) {
25204
- if (!isVisibleOption(option)) return;
25205
- option.node.scrollIntoView({
25206
- block: "nearest",
25207
- inline: "nearest"
25208
- });
25124
+ function scrollOptionIntoView(option) {
25125
+ if (!isVisibleOption(option)) return;
25126
+ option.node.scrollIntoView({
25127
+ block: "nearest",
25128
+ inline: "nearest"
25129
+ });
25209
25130
  }
25210
25131
  function findSearchMatch(text, fromValue = handle.props.activeValue) {
25211
25132
  let interactableOptions = getInteractableOptions();
@@ -25408,168 +25329,10 @@ var optionMixin = createMixin((handle) => {
25408
25329
  ]];
25409
25330
  };
25410
25331
  });
25411
- var Context$1 = ListboxProvider;
25332
+ var Context = ListboxProvider;
25412
25333
  var list = listMixin;
25413
25334
  var option = optionMixin;
25414
25335
  //#endregion
25415
- //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/lib/outside-click.js
25416
- var onOutsideClick = createMixin((handle) => {
25417
- let active = false;
25418
- let handler = () => {};
25419
- let isInsideTarget = () => false;
25420
- let stopPropagation = true;
25421
- handle.addEventListener("insert", (event) => {
25422
- let node = event.node;
25423
- node.ownerDocument.addEventListener("click", (event) => {
25424
- let target = event.target instanceof Node ? event.target : null;
25425
- if (!active || target && (node.contains(target) || isInsideTarget(target))) return;
25426
- if (stopPropagation) event.stopPropagation();
25427
- handler(target);
25428
- }, {
25429
- capture: true,
25430
- signal: handle.signal
25431
- });
25432
- });
25433
- return (nextActive, nextHandler, nextIsInsideTarget = () => false, nextStopPropagation = true) => {
25434
- active = nextActive;
25435
- handler = nextHandler;
25436
- isInsideTarget = nextIsInsideTarget;
25437
- stopPropagation = nextStopPropagation;
25438
- };
25439
- });
25440
- //#endregion
25441
- //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/lib/scroll-lock.js
25442
- var scrollLocks = /* @__PURE__ */ new WeakMap();
25443
- function lockScroll(targetDocument = globalThis.document) {
25444
- if (!targetDocument?.body || !targetDocument.defaultView) return () => {};
25445
- let document = targetDocument;
25446
- let documentElement = document.documentElement;
25447
- let view = document.defaultView;
25448
- let state = scrollLocks.get(document);
25449
- if (!state) {
25450
- let scrollX = view.scrollX;
25451
- let scrollY = view.scrollY;
25452
- let scrollbarWidth = documentElement.clientWidth > 0 ? Math.max(view.innerWidth - documentElement.clientWidth, 0) : 0;
25453
- let computedScrollbarGutter = view.getComputedStyle(documentElement).scrollbarGutter;
25454
- state = {
25455
- count: 0,
25456
- documentOverflow: documentElement.style.overflow,
25457
- documentScrollbarGutter: documentElement.style.scrollbarGutter,
25458
- scrollX,
25459
- scrollY
25460
- };
25461
- documentElement.style.overflow = "hidden";
25462
- if (scrollbarWidth > 0 && computedScrollbarGutter === "auto") documentElement.style.scrollbarGutter = "stable";
25463
- scrollLocks.set(document, state);
25464
- }
25465
- state.count++;
25466
- let unlocked = false;
25467
- return () => {
25468
- if (unlocked) return;
25469
- unlocked = true;
25470
- let currentState = scrollLocks.get(document);
25471
- if (!currentState) return;
25472
- currentState.count--;
25473
- if (currentState.count > 0) return;
25474
- scrollLocks.delete(document);
25475
- documentElement.style.overflow = currentState.documentOverflow;
25476
- documentElement.style.scrollbarGutter = currentState.documentScrollbarGutter;
25477
- view.scrollTo(currentState.scrollX, currentState.scrollY);
25478
- };
25479
- }
25480
- //#endregion
25481
- //#region ../../node_modules/.pnpm/@remix-run+ui@0.12.1/node_modules/@remix-run/ui/dist/popover.js
25482
- function PopoverProvider(handle) {
25483
- handle.context.set({
25484
- hideFocusTarget: null,
25485
- showFocusTarget: null,
25486
- surface: null,
25487
- anchor: null
25488
- });
25489
- return () => handle.props.children;
25490
- }
25491
- var anchorMixin = createMixin((handle) => {
25492
- let context = handle.context.get(PopoverProvider);
25493
- return (options) => {
25494
- handle.queueTask((node) => {
25495
- context.anchor = {
25496
- target: node,
25497
- options
25498
- };
25499
- });
25500
- };
25501
- });
25502
- function anchorContains(anchor, target) {
25503
- return anchor?.target instanceof HTMLElement && anchor.target.contains(target);
25504
- }
25505
- var surfaceMixin = createMixin((handle) => {
25506
- let openProp = false;
25507
- let cleanupAnchor = () => {};
25508
- let unlockScroll = () => {};
25509
- let context = handle.context.get(PopoverProvider);
25510
- handle.queueTask((node, signal) => {
25511
- context.surface = node;
25512
- signal.addEventListener("abort", () => {
25513
- if (context.surface === node) context.surface = null;
25514
- });
25515
- });
25516
- return (options) => {
25517
- let wasOpen = openProp;
25518
- openProp = options.open;
25519
- handle.queueTask(async (node) => {
25520
- if (openProp && !wasOpen) node.showPopover();
25521
- else if (!openProp && wasOpen) node.hidePopover();
25522
- });
25523
- return [
25524
- attrs({ popover: "manual" }),
25525
- on$1("beforetoggle", (event) => {
25526
- if (event.newState === "open") {
25527
- let anchor = context.anchor;
25528
- if (!anchor) throw new Error("popover.surface() requires a registered anchor before opening");
25529
- cleanupAnchor = anchor$1(event.currentTarget, anchor.target, anchor.options);
25530
- unlockScroll = lockScroll();
25531
- } else if (event.newState === "closed") {
25532
- cleanupAnchor();
25533
- unlockScroll();
25534
- }
25535
- }),
25536
- on$1("toggle", async (event) => {
25537
- if (event.newState === "open") context.showFocusTarget?.focus();
25538
- else if (event.newState === "closed" && options.restoreFocusOnHide !== false) context.hideFocusTarget?.focus();
25539
- }),
25540
- on$1("keydown", (event) => {
25541
- if (event.key === "Escape") {
25542
- event.preventDefault();
25543
- options.onHide({ reason: "escape-key" });
25544
- }
25545
- }),
25546
- onOutsideClick(openProp, (target) => {
25547
- options.onHide({
25548
- reason: "outside-click",
25549
- target
25550
- });
25551
- }, (target) => options.closeOnAnchorClick === false && anchorContains(context.anchor, target), options.stopOutsideClickPropagation ?? true)
25552
- ];
25553
- };
25554
- });
25555
- var focusOnShowMixin = createMixin((handle) => {
25556
- handle.addEventListener("insert", (event) => {
25557
- let context = handle.context.get(PopoverProvider);
25558
- context.showFocusTarget = event.node;
25559
- });
25560
- });
25561
- var focusOnHideMixin = createMixin((handle) => {
25562
- handle.addEventListener("insert", (event) => {
25563
- let context = handle.context.get(PopoverProvider);
25564
- context.hideFocusTarget = event.node;
25565
- });
25566
- });
25567
- var Context = PopoverProvider;
25568
- var anchor = anchorMixin;
25569
- var surface = surfaceMixin;
25570
- var focusOnHide = focusOnHideMixin;
25571
- var focusOnShow = focusOnShowMixin;
25572
- //#endregion
25573
25336
  //#region ../ui/src/lib/select.tsx
25574
25337
  /** The popup surface shared by selects and menus. */
25575
25338
  var menuSurface = "bud-overlay bud-menu m-0 min-w-44 rounded-lg p-1 text-ink";
@@ -25635,7 +25398,7 @@ function Select(handle) {
25635
25398
  let summary = current.length === 0 ? props.placeholder ?? "" : current.length === 1 ? current[0].label : `${current[0].label} +${current.length - 1}`;
25636
25399
  let leading = current.length === 1 && current[0].icon ? current[0].icon : props.icon;
25637
25400
  let search = props.search ? typeof props.search === "string" ? props.search : t("select.searchPlaceholder") : void 0;
25638
- return /* @__PURE__ */ jsxs(Context, { children: [
25401
+ return /* @__PURE__ */ jsxs(Context$1, { children: [
25639
25402
  /* @__PURE__ */ jsx(HeadlessButton, {
25640
25403
  "aria-label": props.label,
25641
25404
  "aria-haspopup": "listbox",
@@ -25726,7 +25489,7 @@ function Select(handle) {
25726
25489
  })
25727
25490
  ]
25728
25491
  })]
25729
- }) : null, /* @__PURE__ */ jsx(Context$1, {
25492
+ }) : null, /* @__PURE__ */ jsx(Context, {
25730
25493
  ref: (value) => {
25731
25494
  list$2 = value;
25732
25495
  },
@@ -25799,6 +25562,403 @@ function Select(handle) {
25799
25562
  };
25800
25563
  }
25801
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
25802
25962
  //#region ../ui/src/lib/tasks/task-draft.ts
25803
25963
  function editValue(value, field) {
25804
25964
  return field.kind === "json" ? JSON.stringify(value ?? {}, null, 2) : taskValueText(value);