@protobase/ui 0.19.4 → 0.19.6

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 (159) hide show
  1. package/dist/account/account-section.js +2 -2
  2. package/dist/account/account-security-page.js +2 -2
  3. package/dist/account/backup-codes.js +3 -10
  4. package/dist/account/passkeys-section.js +2 -2
  5. package/dist/account/password-dialog.js +1 -1
  6. package/dist/account/sign-in-policy-form.js +2 -2
  7. package/dist/account/sign-in-policy-page.js +2 -2
  8. package/dist/account/sign-in-providers-section.js +1 -1
  9. package/dist/account/staff-sign-in-log.js +3 -3
  10. package/dist/account/two-factor-setup.js +6 -6
  11. package/dist/action-card/action-card.js +1 -1
  12. package/dist/activity-chart/activity-chart.js +2 -2
  13. package/dist/app/delete/dialogs.js +3 -3
  14. package/dist/app/displays/display-controls.js +1 -1
  15. package/dist/app/displays/display-frame.js +1 -1
  16. package/dist/app/displays/list-cards.js +1 -1
  17. package/dist/app/displays/list-displays.js +2 -1
  18. package/dist/app/displays/map-display.js +1 -1
  19. package/dist/app/displays/record-card.js +1 -1
  20. package/dist/app/embedded-list/embedded-list.js +2 -2
  21. package/dist/app/error-banner.js +1 -1
  22. package/dist/app/field-editor.js +6 -12
  23. package/dist/app/file-field.js +4 -4
  24. package/dist/app/filled-fields.js +5 -0
  25. package/dist/app/gallery/details-open-storage.js +10 -0
  26. package/dist/app/gallery/gallery-layout.js +3 -1
  27. package/dist/app/gallery/lightbox.js +107 -61
  28. package/dist/app/gallery/photo-details.js +1 -1
  29. package/dist/app/import/convert.js +151 -0
  30. package/dist/app/import/csv-parser.js +119 -0
  31. package/dist/app/import/failed-rows.js +21 -0
  32. package/dist/app/import/import-button.js +31 -0
  33. package/dist/app/import/import-check-step.js +123 -0
  34. package/dist/app/import/import-counts.js +19 -0
  35. package/dist/app/import/import-dialog.js +152 -0
  36. package/dist/app/import/import-file-step.js +65 -0
  37. package/dist/app/import/import-mapping-step.js +195 -0
  38. package/dist/app/import/import-result-step.js +86 -0
  39. package/dist/app/import/import-select.js +35 -0
  40. package/dist/app/import/locale-formats.js +15 -0
  41. package/dist/app/import/lookups.js +48 -0
  42. package/dist/app/import/mapping.js +78 -0
  43. package/dist/app/import/parse-worker.d.ts +1 -0
  44. package/dist/app/import/parse-worker.js +39 -0
  45. package/dist/app/import/prepare-rows.js +129 -0
  46. package/dist/app/import/run-import.js +130 -0
  47. package/dist/app/import/sheet-cell.js +17 -0
  48. package/dist/app/import/sheet-file.js +58 -0
  49. package/dist/app/import/sheet-source.js +72 -0
  50. package/dist/app/import/use-import-flow.js +225 -0
  51. package/dist/app/import/xlsx-reader.js +43 -0
  52. package/dist/app/import/xlsx-xml.js +169 -0
  53. package/dist/app/import/zip-reader.js +72 -0
  54. package/dist/app/list-page.js +8 -1
  55. package/dist/app/meta-gate.js +3 -3
  56. package/dist/app/named-icon.js +9 -1
  57. package/dist/app/pages/blocks/card-frame.js +4 -4
  58. package/dist/app/pages/blocks/field.js +2 -5
  59. package/dist/app/pages/blocks/stat.js +2 -2
  60. package/dist/app/pages/problem.js +1 -1
  61. package/dist/app/pages/render-node.js +1 -1
  62. package/dist/app/record-page.js +13 -4
  63. package/dist/app/record-row/record-row-body.js +3 -3
  64. package/dist/app/record-row/saved-record-row.js +1 -1
  65. package/dist/app/related-section.js +1 -1
  66. package/dist/app/relation-picker.js +1 -1
  67. package/dist/app/runtime/runtime-update.js +3 -3
  68. package/dist/app/saved/saved-list.js +1 -1
  69. package/dist/app/searches/saved-search-rows.js +2 -2
  70. package/dist/app/searches/saved-search-section.js +1 -1
  71. package/dist/app/searches/saved-searches.js +1 -1
  72. package/dist/app/searches/search-controls.js +2 -2
  73. package/dist/app/searches/search-name-dialog.js +1 -1
  74. package/dist/app/searches/update-search-dialog.js +16 -19
  75. package/dist/app/sensitive-field.js +1 -1
  76. package/dist/app/source-notice.js +3 -3
  77. package/dist/app/staff-banner.js +1 -1
  78. package/dist/app-shell/breadcrumb.js +1 -1
  79. package/dist/app-shell/global-search.js +1 -1
  80. package/dist/app-shell/page-header.js +1 -1
  81. package/dist/app-shell/profile-menu.js +3 -3
  82. package/dist/app-shell/sidebar-group.js +1 -1
  83. package/dist/app-shell/sidebar-item.js +1 -1
  84. package/dist/app-shell/sidebar.js +2 -2
  85. package/dist/assistant-dock/part-view.js +1 -4
  86. package/dist/assistant-dock/widget-view.js +1 -4
  87. package/dist/auth/auth-gate.js +2 -2
  88. package/dist/auth/auth-layout.js +1 -1
  89. package/dist/auth/code-form.js +2 -2
  90. package/dist/auth/first-run-page.js +1 -1
  91. package/dist/auth/forgot-password-form.js +2 -2
  92. package/dist/auth/provider-sign-in-page.js +1 -1
  93. package/dist/auth/reset-password-form.js +3 -3
  94. package/dist/auth/setup-required-form.js +2 -2
  95. package/dist/auth/setup-required-page.js +1 -1
  96. package/dist/auth/sign-in-form.js +5 -5
  97. package/dist/auth/sign-in-prompt.js +3 -3
  98. package/dist/auth/sign-up-details-form.js +3 -3
  99. package/dist/auth/sign-up-email-form.js +4 -4
  100. package/dist/auth/staff-sign-in-form.js +3 -3
  101. package/dist/auth/two-factor-form.js +5 -5
  102. package/dist/chat/chat-message.js +1 -1
  103. package/dist/chat/chat-thread.js +1 -1
  104. package/dist/data/api-provider.js +8 -2
  105. package/dist/data-grid/grid-row.js +1 -1
  106. package/dist/data-grid/header-menu.js +1 -1
  107. package/dist/data-grid/row-menu.js +1 -1
  108. package/dist/filter-panel/facet-combobox.js +1 -1
  109. package/dist/filter-panel/filter-menu.js +1 -1
  110. package/dist/filter-panel/filter-panel.js +2 -2
  111. package/dist/inline-child-table/line-menu.js +1 -1
  112. package/dist/inline-child-table/line-row.js +4 -4
  113. package/dist/inline-child-table/number-cell.js +1 -1
  114. package/dist/inline-child-table/totals-footer.js +2 -2
  115. package/dist/lib/save-file.js +12 -0
  116. package/dist/lib/use-focus-trap.js +7 -1
  117. package/dist/organizations/app-roles-picker.js +1 -1
  118. package/dist/organizations/invitation-page.js +3 -3
  119. package/dist/organizations/invitations-page.js +2 -2
  120. package/dist/organizations/members-page.js +2 -2
  121. package/dist/organizations/organization-page.js +1 -1
  122. package/dist/organizations/organizations-page.js +2 -2
  123. package/dist/package.js +1 -1
  124. package/dist/plugins/before-sign-in.js +1 -1
  125. package/dist/plugins/plugin-gate.js +1 -1
  126. package/dist/plugins/plugin-notice.js +1 -1
  127. package/dist/primitives/button.js +1 -1
  128. package/dist/primitives/dialog.js +2 -2
  129. package/dist/primitives/input.js +1 -1
  130. package/dist/primitives/menu-select.js +2 -2
  131. package/dist/primitives/responsive-dialog.js +1 -1
  132. package/dist/primitives/segmented.js +1 -1
  133. package/dist/primitives/sheet-swipe.js +5 -0
  134. package/dist/primitives/sheet.js +40 -12
  135. package/dist/primitives/textarea.js +1 -1
  136. package/dist/record-view/action-rail.js +1 -1
  137. package/dist/record-view/field.js +1 -1
  138. package/dist/record-view/key-facts.js +29 -0
  139. package/dist/record-view/record-sidebar.js +2 -2
  140. package/dist/record-view/section.js +2 -2
  141. package/dist/record-view/spec-table.js +1 -1
  142. package/dist/shell/assets/app-DwKzLvfm.js +67 -0
  143. package/dist/shell/assets/index-CwhnZIJp.css +2 -0
  144. package/dist/shell/assets/{index-CEvfMgFJ.js → index-Dqgcyg7M.js} +1 -1
  145. package/dist/shell/assets/{maplibre-D6zfLfTV.js → maplibre-BWjNhvrH.js} +1 -1
  146. package/dist/shell/assets/parse-worker-BtcY0dQw.js +5 -0
  147. package/dist/shell/assets/{record-map-B6_rbCcG.js → record-map-m5K2uttt.js} +1 -1
  148. package/dist/shell/assets/{records-map-bQbuIc13.js → records-map-CA_JG9RR.js} +1 -1
  149. package/dist/shell/assets/{shared-protobase-ui-8pJgwjfc.js → shared-protobase-ui-BBqKlVLh.js} +1 -1
  150. package/dist/shell/index.html +4 -4
  151. package/dist/shell/protobase.shell.json +1 -1
  152. package/dist/side-panel/side-panel.js +1 -1
  153. package/dist/step-list/step-list.js +1 -1
  154. package/dist/styles.css +1 -1
  155. package/dist/theme.css +2 -0
  156. package/dist/toasts/toast.js +2 -2
  157. package/package.json +4 -4
  158. package/dist/shell/assets/app-BYFiTXK6.js +0 -66
  159. package/dist/shell/assets/index-CiGP6kle.css +0 -2
@@ -78,7 +78,7 @@ const SignInForm = ({ onSubmit, busy, error, workspace, notice, methods = ["pass
78
78
  },
79
79
  children: [
80
80
  /* @__PURE__ */ jsxs("label", {
81
- className: "flex flex-col gap-1.5 text-[13px] font-medium",
81
+ className: "flex flex-col gap-1.5 font-medium",
82
82
  children: ["Email", /* @__PURE__ */ jsx(Input, {
83
83
  ref: emailInput,
84
84
  type: "email",
@@ -93,7 +93,7 @@ const SignInForm = ({ onSubmit, busy, error, workspace, notice, methods = ["pass
93
93
  })]
94
94
  }),
95
95
  withPassword && /* @__PURE__ */ jsxs("label", {
96
- className: "flex flex-col gap-1.5 text-[13px] font-medium",
96
+ className: "flex flex-col gap-1.5 font-medium",
97
97
  children: ["Password", /* @__PURE__ */ jsx(Input, {
98
98
  type: "password",
99
99
  name: "password",
@@ -128,13 +128,13 @@ const SignInForm = ({ onSubmit, busy, error, workspace, notice, methods = ["pass
128
128
  }),
129
129
  withPassword && forgotPassword && /* @__PURE__ */ jsx(RouteLink, {
130
130
  to: forgotPassword,
131
- className: "self-center text-[13px] font-medium text-primary-text hover:underline",
131
+ className: "self-center font-medium text-primary-text hover:underline",
132
132
  children: "Forgot password?"
133
133
  })
134
134
  ]
135
135
  }),
136
136
  signUp && /* @__PURE__ */ jsxs("p", {
137
- className: "mt-4 text-center text-[13px] text-muted-foreground",
137
+ className: "mt-4 text-center text-muted-foreground",
138
138
  children: [
139
139
  "New here?",
140
140
  " ",
@@ -148,7 +148,7 @@ const SignInForm = ({ onSubmit, busy, error, workspace, notice, methods = ["pass
148
148
  onBrowse && /* @__PURE__ */ jsx("button", {
149
149
  type: "button",
150
150
  onClick: onBrowse,
151
- className: "mt-4 w-full text-center text-[13px] font-medium text-muted-foreground hover:text-foreground hover:underline",
151
+ className: "mt-4 w-full text-center font-medium text-muted-foreground hover:text-foreground hover:underline",
152
152
  children: "Continue without signing in"
153
153
  })
154
154
  ]
@@ -2,14 +2,14 @@ import { RouteLink } from "../primitives/route-link.js";
2
2
  import { useAuth } from "./auth-provider.js";
3
3
  import { Button, buttonClass } from "../primitives/button.js";
4
4
  import { useSignInAddress } from "./use-sign-in-address.js";
5
- import { namedIcon } from "../app/named-icon.js";
6
5
  import { ResponsiveDialog } from "../primitives/responsive-dialog.js";
6
+ import { namedIcon } from "../app/named-icon.js";
7
7
  import { useId } from "react";
8
8
  import { Check, LogIn } from "lucide-react";
9
9
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
10
10
  //#region src/auth/sign-in-prompt.tsx
11
11
  const Benefits = ({ benefits }) => benefits && benefits.length > 0 ? /* @__PURE__ */ jsx("ul", {
12
- className: "flex flex-col gap-2 text-left text-[13px]",
12
+ className: "flex flex-col gap-2 text-left",
13
13
  children: benefits.map((benefit) => /* @__PURE__ */ jsxs("li", {
14
14
  className: "flex items-start gap-2",
15
15
  children: [/* @__PURE__ */ jsx(Check, {
@@ -78,7 +78,7 @@ const SignInPrompt = (props) => {
78
78
  className: "text-lg font-semibold",
79
79
  children: prompt.title
80
80
  }), /* @__PURE__ */ jsx("p", {
81
- className: "text-[13px] text-muted-foreground",
81
+ className: "text-muted-foreground",
82
82
  children: prompt.description
83
83
  })]
84
84
  }),
@@ -15,7 +15,7 @@ const SignUpDetailsForm = ({ email, onSubmit, onBack, methods, busy, error }) =>
15
15
  title: "Your details",
16
16
  description: "One last step to create your account.",
17
17
  children: [/* @__PURE__ */ jsxs("p", {
18
- className: "mb-4 text-[13px] text-muted-foreground",
18
+ className: "mb-4 text-muted-foreground",
19
19
  children: [
20
20
  "The code was right: the account is for ",
21
21
  /* @__PURE__ */ jsx("span", {
@@ -35,7 +35,7 @@ const SignUpDetailsForm = ({ email, onSubmit, onBack, methods, busy, error }) =>
35
35
  },
36
36
  children: [
37
37
  /* @__PURE__ */ jsxs("label", {
38
- className: "flex flex-col gap-1.5 text-[13px] font-medium",
38
+ className: "flex flex-col gap-1.5 font-medium",
39
39
  children: ["Your name", /* @__PURE__ */ jsx(Input, {
40
40
  name: "name",
41
41
  autoComplete: "name",
@@ -50,7 +50,7 @@ const SignUpDetailsForm = ({ email, onSubmit, onBack, methods, busy, error }) =>
50
50
  withPassword && /* @__PURE__ */ jsxs("div", {
51
51
  className: "flex flex-col gap-1.5",
52
52
  children: [/* @__PURE__ */ jsxs("label", {
53
- className: "flex flex-col gap-1.5 text-[13px] font-medium",
53
+ className: "flex flex-col gap-1.5 font-medium",
54
54
  children: [passwordOptional ? "Password (optional)" : "Password", /* @__PURE__ */ jsx(Input, {
55
55
  type: "password",
56
56
  name: "password",
@@ -19,7 +19,7 @@ const SignUpEmailForm = ({ onSubmit, onSignIn, onBrowse, busy, error, workspace
19
19
  },
20
20
  children: [
21
21
  /* @__PURE__ */ jsxs("label", {
22
- className: "flex flex-col gap-1.5 text-[13px] font-medium",
22
+ className: "flex flex-col gap-1.5 font-medium",
23
23
  children: ["Email", /* @__PURE__ */ jsx(Input, {
24
24
  type: "email",
25
25
  name: "email",
@@ -33,7 +33,7 @@ const SignUpEmailForm = ({ onSubmit, onSignIn, onBrowse, busy, error, workspace
33
33
  })]
34
34
  }),
35
35
  /* @__PURE__ */ jsx("p", {
36
- className: "text-[13px] text-muted-foreground",
36
+ className: "text-muted-foreground",
37
37
  children: "We email you a code to make sure the address is yours."
38
38
  }),
39
39
  error && /* @__PURE__ */ jsx("p", {
@@ -49,7 +49,7 @@ const SignUpEmailForm = ({ onSubmit, onSignIn, onBrowse, busy, error, workspace
49
49
  children: [/* @__PURE__ */ jsx(Mail, { className: "size-4" }), "Email me a code"]
50
50
  }),
51
51
  /* @__PURE__ */ jsxs("p", {
52
- className: "text-center text-[13px] text-muted-foreground",
52
+ className: "text-center text-muted-foreground",
53
53
  children: [
54
54
  "Have an account?",
55
55
  " ",
@@ -65,7 +65,7 @@ const SignUpEmailForm = ({ onSubmit, onSignIn, onBrowse, busy, error, workspace
65
65
  }), onBrowse && /* @__PURE__ */ jsx("button", {
66
66
  type: "button",
67
67
  onClick: onBrowse,
68
- className: "mt-4 w-full text-center text-[13px] font-medium text-muted-foreground hover:text-foreground hover:underline",
68
+ className: "mt-4 w-full text-center font-medium text-muted-foreground hover:text-foreground hover:underline",
69
69
  children: "Continue without an account"
70
70
  })]
71
71
  });
@@ -35,7 +35,7 @@ const StaffSignInForm = ({ operator, email: givenEmail = "", reason: givenReason
35
35
  },
36
36
  children: [
37
37
  /* @__PURE__ */ jsxs("label", {
38
- className: "flex flex-col gap-1.5 text-[13px] font-medium",
38
+ className: "flex flex-col gap-1.5 font-medium",
39
39
  children: ["Their email", /* @__PURE__ */ jsx(Input, {
40
40
  type: "email",
41
41
  name: "email",
@@ -47,7 +47,7 @@ const StaffSignInForm = ({ operator, email: givenEmail = "", reason: givenReason
47
47
  })]
48
48
  }),
49
49
  /* @__PURE__ */ jsxs("label", {
50
- className: "flex flex-col gap-1.5 text-[13px] font-medium",
50
+ className: "flex flex-col gap-1.5 font-medium",
51
51
  children: ["Reason", /* @__PURE__ */ jsx("textarea", {
52
52
  name: "reason",
53
53
  required: true,
@@ -56,7 +56,7 @@ const StaffSignInForm = ({ operator, email: givenEmail = "", reason: givenReason
56
56
  value: reason,
57
57
  placeholder: "Ticket 4211: the invoice totals look wrong",
58
58
  onChange: (event) => setReason(event.target.value),
59
- className: cn("resize-y rounded-md border bg-background px-2.5 py-1.5 text-[13px] font-normal shadow-sm outline-none placeholder:text-faint-foreground focus:border-primary focus:ring-2 focus:ring-primary/20", error ? "border-danger" : "border-border-strong")
59
+ className: cn("resize-y rounded-md border bg-background px-2.5 py-1.5 font-normal shadow-sm outline-none placeholder:text-faint-foreground focus:border-primary focus:ring-2 focus:ring-primary/20", error ? "border-danger" : "border-border-strong")
60
60
  })]
61
61
  }),
62
62
  /* @__PURE__ */ jsxs("p", {
@@ -29,7 +29,7 @@ const TwoFactorForm = ({ methods, onVerify, onSendCode, onBack, busy, error, cod
29
29
  className: "flex flex-col gap-4",
30
30
  children: [
31
31
  /* @__PURE__ */ jsx("p", {
32
- className: "text-[13px] text-muted-foreground",
32
+ className: "text-muted-foreground",
33
33
  children: "We email you a code to finish signing in."
34
34
  }),
35
35
  error && /* @__PURE__ */ jsx("p", {
@@ -57,11 +57,11 @@ const TwoFactorForm = ({ methods, onVerify, onSendCode, onBack, busy, error, cod
57
57
  },
58
58
  children: [
59
59
  /* @__PURE__ */ jsx("p", {
60
- className: "text-[13px] text-muted-foreground",
60
+ className: "text-muted-foreground",
61
61
  children: instructions[step]
62
62
  }),
63
63
  /* @__PURE__ */ jsxs("label", {
64
- className: "flex flex-col gap-1.5 text-[13px] font-medium",
64
+ className: "flex flex-col gap-1.5 font-medium",
65
65
  children: [step === "backup" ? "Backup code" : "Code", step === "backup" ? /* @__PURE__ */ jsx(Input, {
66
66
  name: "code",
67
67
  autoComplete: "off",
@@ -82,7 +82,7 @@ const TwoFactorForm = ({ methods, onVerify, onSendCode, onBack, busy, error, cod
82
82
  })]
83
83
  }),
84
84
  /* @__PURE__ */ jsxs("label", {
85
- className: "flex items-center gap-2 text-[13px] text-muted-foreground",
85
+ className: "flex items-center gap-2 text-muted-foreground",
86
86
  children: [/* @__PURE__ */ jsx(Checkbox, {
87
87
  checked: trustDevice,
88
88
  onChange: setTrustDevice,
@@ -103,7 +103,7 @@ const TwoFactorForm = ({ methods, onVerify, onSendCode, onBack, busy, error, cod
103
103
  })
104
104
  ]
105
105
  }), /* @__PURE__ */ jsxs("div", {
106
- className: "mt-4 flex flex-col items-center gap-2 border-t pt-4 text-[13px]",
106
+ className: "mt-4 flex flex-col items-center gap-2 border-t pt-4",
107
107
  children: [
108
108
  step !== "totp" && methods.includes("totp") && /* @__PURE__ */ jsx("button", {
109
109
  type: "button",
@@ -4,7 +4,7 @@ import { jsx } from "react/jsx-runtime";
4
4
  /** One message of a chat: the user's as a bubble on the right, the other side's as plain content, cards included. */
5
5
  const ChatMessage = ({ from, children }) => /* @__PURE__ */ jsx("div", {
6
6
  "data-from": from,
7
- className: cn("flex min-w-0 flex-col gap-2 whitespace-pre-wrap break-words text-[13px]", from === "user" ? "ml-8 self-end rounded-lg rounded-br-sm bg-primary px-3 py-2 text-primary-foreground shadow-sm" : "mr-4 text-foreground"),
7
+ className: cn("flex min-w-0 flex-col gap-2 whitespace-pre-wrap break-words", from === "user" ? "ml-8 self-end rounded-lg rounded-br-sm bg-primary px-3 py-2 text-primary-foreground shadow-sm" : "mr-4 text-foreground"),
8
8
  children
9
9
  });
10
10
  //#endregion
@@ -14,7 +14,7 @@ const ChatThread = ({ children, empty, length }) => {
14
14
  "aria-live": "polite",
15
15
  className: "flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4",
16
16
  children: length === 0 && empty ? /* @__PURE__ */ jsx("div", {
17
- className: "m-auto max-w-60 text-center text-[13px] text-muted-foreground",
17
+ className: "m-auto max-w-60 text-center text-muted-foreground",
18
18
  children: empty
19
19
  }) : children
20
20
  });
@@ -1,5 +1,5 @@
1
1
  import { keys } from "./query-keys.js";
2
- import { createContext, useContext, useMemo, useRef, useState } from "react";
2
+ import { createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
3
3
  import { createClient } from "@protobase/client";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
@@ -21,6 +21,12 @@ const useResourceClient = (resource) => {
21
21
  const own = useClient();
22
22
  return useContext(ResourceClientContext)?.(resource) ?? own;
23
23
  };
24
+ /** `useResourceClient` for any resource: the client of the API that serves each, for code that reaches several. */
25
+ const useResourceClients = () => {
26
+ const own = useClient();
27
+ const resourceClient = useContext(ResourceClientContext);
28
+ return useCallback((resource) => resourceClient?.(resource) ?? own, [own, resourceClient]);
29
+ };
24
30
  /** The client of a source's API, one per URL, with the signed-in user's token; for `MetaGate`. */
25
31
  const useSourceClients = () => {
26
32
  const clientOf = useContext(SourceClientsContext);
@@ -104,4 +110,4 @@ const ApiProvider = ({ baseUrl, client: given, sources, fetch, queryClient: give
104
110
  });
105
111
  };
106
112
  //#endregion
107
- export { ApiProvider, ClientProvider, useClient, useResourceClient, useSourceClients };
113
+ export { ApiProvider, ClientProvider, useClient, useResourceClient, useResourceClients, useSourceClients };
@@ -33,7 +33,7 @@ const Gutter = ({ width, index, selectable, selected, onToggle, edge }) => /* @_
33
33
  const DataRow = ({ cells, selected, onToggle, onOpen, menu, ...frame }) => /* @__PURE__ */ jsxs("div", {
34
34
  role: "row",
35
35
  onClick: onOpen,
36
- className: cn("absolute inset-x-0 top-0 grid items-center border-b text-[13px]", selected ? "bg-primary-soft" : "bg-background hover:bg-surface", onOpen && "cursor-pointer"),
36
+ className: cn("absolute inset-x-0 top-0 grid items-center border-b", selected ? "bg-primary-soft" : "bg-background hover:bg-surface", onOpen && "cursor-pointer"),
37
37
  style: frameStyle(frame),
38
38
  children: [
39
39
  /* @__PURE__ */ jsx(Gutter, {
@@ -31,7 +31,7 @@ const HeaderMenu = ({ header, pinned, onTogglePin }) => {
31
31
  children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "size-3.5" })
32
32
  }), open && /* @__PURE__ */ jsx("ul", {
33
33
  role: "menu",
34
- className: "absolute right-0 top-full z-30 mt-1 min-w-32 rounded-md border bg-background py-1 text-[13px] font-normal text-foreground shadow-pop",
34
+ className: "absolute right-0 top-full z-30 mt-1 min-w-32 rounded-md border bg-background py-1 text-body font-normal text-foreground shadow-pop",
35
35
  children: /* @__PURE__ */ jsx("li", {
36
36
  role: "none",
37
37
  children: /* @__PURE__ */ jsx("button", {
@@ -67,7 +67,7 @@ const RowMenu = ({ row, actions }) => {
67
67
  role: "menu",
68
68
  onClick: (event) => event.stopPropagation(),
69
69
  style: place ?? { visibility: "hidden" },
70
- className: "fixed z-50 min-w-36 rounded-md border bg-background py-1 text-[13px] shadow-pop",
70
+ className: "fixed z-50 min-w-36 rounded-md border bg-background py-1 shadow-pop",
71
71
  children: actions.map((action) => /* @__PURE__ */ jsx("li", {
72
72
  role: "none",
73
73
  children: /* @__PURE__ */ jsx("button", {
@@ -74,7 +74,7 @@ const FacetCombobox = ({ config, selected, onToggle, inline = false }) => {
74
74
  "aria-autocomplete": "list",
75
75
  "aria-activedescendant": options[active] ? `${id}-${active}` : void 0,
76
76
  placeholder: selected.length > 0 ? "" : `Search ${config.label.toLowerCase()}`,
77
- className: "h-6 min-w-16 flex-1 bg-transparent text-[13px] text-foreground outline-none placeholder:text-faint-foreground",
77
+ className: "h-6 min-w-16 flex-1 bg-transparent text-foreground outline-none placeholder:text-faint-foreground",
78
78
  value: query,
79
79
  onChange: (event) => search(event.target.value),
80
80
  onKeyDown,
@@ -26,7 +26,7 @@ const FilterMenu = ({ label, activeCount, width = 240, defaultOpen = false, chil
26
26
  type: "button",
27
27
  "aria-expanded": open,
28
28
  onClick: () => setOpen(!open),
29
- className: cn("inline-flex h-8 items-center gap-1.5 rounded-md border bg-background px-2.5 text-[13px] font-medium shadow-sm transition-colors hover:bg-muted", activeCount > 0 ? "border-primary-border text-primary-text" : "border-border-strong text-foreground"),
29
+ className: cn("inline-flex h-8 items-center gap-1.5 rounded-md border bg-background px-2.5 font-medium shadow-sm transition-colors hover:bg-muted", activeCount > 0 ? "border-primary-border text-primary-text" : "border-border-strong text-foreground"),
30
30
  children: [
31
31
  label,
32
32
  activeCount > 0 && /* @__PURE__ */ jsx("span", {
@@ -60,7 +60,7 @@ const FilterPanel = ({ config, layout, value, defaultValue, onChange, lockedLabe
60
60
  /* @__PURE__ */ jsx("div", {
61
61
  className: "flex items-center justify-between pb-1",
62
62
  children: /* @__PURE__ */ jsx("h2", {
63
- className: "text-[13px] font-semibold",
63
+ className: "font-semibold",
64
64
  children: "Filters"
65
65
  })
66
66
  }),
@@ -105,7 +105,7 @@ const FilterPanel = ({ config, layout, value, defaultValue, onChange, lockedLabe
105
105
  ...dateView ? [/* @__PURE__ */ jsx(Fragment, { children: dateView }, "date")] : []
106
106
  ];
107
107
  const toggles = (config.toggles ?? []).map((toggle) => /* @__PURE__ */ jsx("div", {
108
- className: "flex items-center gap-2 text-[13px] text-muted-foreground",
108
+ className: "flex items-center gap-2 text-muted-foreground",
109
109
  children: /* @__PURE__ */ jsx(ToggleRow, {
110
110
  label: toggle.label,
111
111
  checked: state.toggles.includes(toggle.id),
@@ -32,7 +32,7 @@ const LineMenu = ({ items, label }) => {
32
32
  children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "size-4" })
33
33
  }), open && /* @__PURE__ */ jsx("ul", {
34
34
  role: "menu",
35
- className: "absolute right-0 top-full z-30 mt-1 min-w-36 rounded-md border bg-background py-1 text-[13px] shadow-pop",
35
+ className: "absolute right-0 top-full z-30 mt-1 min-w-36 rounded-md border bg-background py-1 shadow-pop",
36
36
  children: items.map((item) => /* @__PURE__ */ jsx("li", {
37
37
  role: "none",
38
38
  children: /* @__PURE__ */ jsx("button", {
@@ -49,7 +49,7 @@ const LineRow = ({ line, position, count, reorderable, editable, onChange, onRem
49
49
  /* @__PURE__ */ jsxs("div", {
50
50
  className: "min-w-0",
51
51
  children: [/* @__PURE__ */ jsx("p", {
52
- className: "truncate text-[13px] text-foreground",
52
+ className: "truncate text-foreground",
53
53
  children: line.description
54
54
  }), /* @__PURE__ */ jsx("p", {
55
55
  className: "font-mono text-[11px] text-faint-foreground",
@@ -63,7 +63,7 @@ const LineRow = ({ line, position, count, reorderable, editable, onChange, onRem
63
63
  parse: (text) => Math.round(Number(text)),
64
64
  onCommit: (quantity) => onChange({ quantity })
65
65
  }) : /* @__PURE__ */ jsx("span", {
66
- className: "px-2 text-right text-[13px] tabular-nums",
66
+ className: "px-2 text-right tabular-nums",
67
67
  children: line.quantity
68
68
  }),
69
69
  editable ? /* @__PURE__ */ jsx(NumberCell, {
@@ -73,11 +73,11 @@ const LineRow = ({ line, position, count, reorderable, editable, onChange, onRem
73
73
  parse: parseEuros,
74
74
  onCommit: (unitPriceCents) => onChange({ unitPriceCents })
75
75
  }) : /* @__PURE__ */ jsx("span", {
76
- className: "px-2 text-right text-[13px] tabular-nums",
76
+ className: "px-2 text-right tabular-nums",
77
77
  children: (line.unitPriceCents / 100).toFixed(2)
78
78
  }),
79
79
  /* @__PURE__ */ jsx("span", {
80
- className: "px-2 text-right text-[13px] font-medium tabular-nums text-foreground",
80
+ className: "px-2 text-right font-medium tabular-nums text-foreground",
81
81
  children: formatMoney(lineTotalCents(line))
82
82
  }),
83
83
  menu.length > 0 ? /* @__PURE__ */ jsx(LineMenu, {
@@ -18,7 +18,7 @@ const NumberCell = ({ value, onCommit, format, parse, label, width = "w-full" })
18
18
  onChange: (event) => setDraft(event.target.value),
19
19
  onBlur: commit,
20
20
  onKeyDown: (event) => event.key === "Enter" && event.currentTarget.blur(),
21
- className: cn("h-7 rounded-md border border-transparent bg-transparent px-2 text-right text-[13px] tabular-nums text-foreground outline-none hover:border-border-strong focus:border-primary focus:bg-background focus:ring-2 focus:ring-primary/20", width)
21
+ className: cn("h-7 rounded-md border border-transparent bg-transparent px-2 text-right tabular-nums text-foreground outline-none hover:border-border-strong focus:border-primary focus:bg-background focus:ring-2 focus:ring-primary/20", width)
22
22
  });
23
23
  };
24
24
  //#endregion
@@ -5,7 +5,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
5
5
  const TotalsFooter = ({ lines, vatRate }) => {
6
6
  const totals = invoiceTotals(lines, vatRate);
7
7
  return /* @__PURE__ */ jsxs("dl", {
8
- className: "ml-auto grid w-64 grid-cols-[1fr_auto] gap-x-6 gap-y-1 px-2 pt-3 text-[13px] tabular-nums",
8
+ className: "ml-auto grid w-64 grid-cols-[1fr_auto] gap-x-6 gap-y-1 px-2 pt-3 tabular-nums",
9
9
  children: [
10
10
  /* @__PURE__ */ jsx("dt", {
11
11
  className: "text-muted-foreground",
@@ -32,7 +32,7 @@ const TotalsFooter = ({ lines, vatRate }) => {
32
32
  children: "Total"
33
33
  }),
34
34
  /* @__PURE__ */ jsx("dd", {
35
- className: "mt-1 border-t border-border pt-2 text-right text-[15px] font-semibold text-foreground",
35
+ className: "mt-1 border-t border-border pt-2 text-right text-[16px] font-semibold text-foreground",
36
36
  children: formatMoney(totals.totalCents)
37
37
  })
38
38
  ]
@@ -0,0 +1,12 @@
1
+ //#region src/lib/save-file.ts
2
+ /** Hands the browser a file to save under `name`, made from `text`. */
3
+ const saveFile = (text, name, type = "text/plain") => {
4
+ const url = URL.createObjectURL(new Blob([text], { type }));
5
+ Object.assign(document.createElement("a"), {
6
+ href: url,
7
+ download: name
8
+ }).click();
9
+ URL.revokeObjectURL(url);
10
+ };
11
+ //#endregion
12
+ export { saveFile };
@@ -2,9 +2,11 @@ import { lockPageScroll } from "./page-scroll-lock.js";
2
2
  import { useEffect, useRef } from "react";
3
3
  //#region src/lib/use-focus-trap.ts
4
4
  const focusable = "a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])";
5
+ const stack = [];
5
6
  /**
6
7
  * While `active`: moves focus into `container`, keeps Tab inside it, locks page scrolling (see `lockPageScroll`),
7
- * calls `onEscape` on Escape, and returns focus to the previously focused element afterwards.
8
+ * calls `onEscape` on Escape, and returns focus to the previously focused element afterwards. A trap opened while
9
+ * another is active takes Tab and Escape until it ends.
8
10
  */
9
11
  const useFocusTrap = (container, active, onEscape) => {
10
12
  const escape = useRef(onEscape);
@@ -14,9 +16,12 @@ const useFocusTrap = (container, active, onEscape) => {
14
16
  const root = container.current;
15
17
  const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
16
18
  const unlock = lockPageScroll();
19
+ const token = {};
20
+ stack.push(token);
17
21
  const items = () => [...root.querySelectorAll(focusable)];
18
22
  (items()[0] ?? root).focus();
19
23
  const onKey = (event) => {
24
+ if (stack.at(-1) !== token) return;
20
25
  if (event.key === "Escape") return escape.current();
21
26
  if (event.key !== "Tab") return;
22
27
  const list = items();
@@ -37,6 +42,7 @@ const useFocusTrap = (container, active, onEscape) => {
37
42
  document.addEventListener("keydown", onKey);
38
43
  return () => {
39
44
  document.removeEventListener("keydown", onKey);
45
+ stack.splice(stack.indexOf(token), 1);
40
46
  unlock();
41
47
  previous?.focus();
42
48
  };
@@ -10,7 +10,7 @@ const AppRolesPicker = ({ roles, selected, grantable, disabled, onChange }) => /
10
10
  const locked = disabled || !grantable.has(role.name);
11
11
  return /* @__PURE__ */ jsxs("label", {
12
12
  title: locked && !disabled ? "You cannot give or take away this role" : void 0,
13
- className: cn("inline-flex items-center gap-1.5 text-[13px]", locked && "text-muted-foreground"),
13
+ className: cn("inline-flex items-center gap-1.5", locked && "text-muted-foreground"),
14
14
  children: [/* @__PURE__ */ jsx(Checkbox, {
15
15
  checked: held,
16
16
  label: role.label,
@@ -35,7 +35,7 @@ const SignUp = ({ token, preview, onDone }) => {
35
35
  },
36
36
  children: [
37
37
  /* @__PURE__ */ jsxs("p", {
38
- className: "text-[13px] text-muted-foreground",
38
+ className: "text-muted-foreground",
39
39
  children: [
40
40
  "Make your account for ",
41
41
  preview.email,
@@ -96,7 +96,7 @@ const InvitationPage = ({ token, onDone, signIn }) => {
96
96
  if (!preview) return /* @__PURE__ */ jsx(AuthLayout, {
97
97
  title: "Invitation",
98
98
  children: /* @__PURE__ */ jsxs("div", {
99
- className: "flex items-center justify-center gap-2 text-[13px] text-muted-foreground",
99
+ className: "flex items-center justify-center gap-2 text-muted-foreground",
100
100
  children: [/* @__PURE__ */ jsx(Spinner, {}), " Loading"]
101
101
  })
102
102
  });
@@ -107,7 +107,7 @@ const InvitationPage = ({ token, onDone, signIn }) => {
107
107
  title: `Join ${preview.organization.name}`,
108
108
  description,
109
109
  children: [/* @__PURE__ */ jsxs("p", {
110
- className: "mb-3 text-[13px] text-muted-foreground",
110
+ className: "mb-3 text-muted-foreground",
111
111
  children: [
112
112
  "This invitation is for ",
113
113
  preview.email,
@@ -62,7 +62,7 @@ const InviteForm = ({ current, onSent }) => {
62
62
  "aria-label": "Organization role",
63
63
  value: role,
64
64
  onChange: (event) => setRole(event.target.value),
65
- className: "h-8 rounded-md border border-border-strong bg-background px-2 text-[13px]",
65
+ className: "h-8 rounded-md border border-border-strong bg-background px-2",
66
66
  children: assignableOrganizationRoles(current).map((option) => /* @__PURE__ */ jsx("option", {
67
67
  value: option,
68
68
  children: organizationRoleLabels[option]
@@ -101,7 +101,7 @@ const PendingRow = ({ invitation, organizationId, onChanged }) => {
101
101
  /* @__PURE__ */ jsxs("span", {
102
102
  className: "min-w-0 flex-1",
103
103
  children: [/* @__PURE__ */ jsx("span", {
104
- className: "block truncate text-[13px] font-medium",
104
+ className: "block truncate font-medium",
105
105
  children: invitation.email
106
106
  }), /* @__PURE__ */ jsxs("span", {
107
107
  className: "block text-xs text-muted-foreground",
@@ -34,7 +34,7 @@ const MemberRow = ({ member, current, onChanged }) => {
34
34
  /* @__PURE__ */ jsxs("span", {
35
35
  className: "min-w-0 flex-1",
36
36
  children: [/* @__PURE__ */ jsxs("span", {
37
- className: "block truncate text-[13px] font-medium",
37
+ className: "block truncate font-medium",
38
38
  children: [member.name, self && " (you)"]
39
39
  }), /* @__PURE__ */ jsx("span", {
40
40
  className: "block truncate text-xs text-muted-foreground",
@@ -112,7 +112,7 @@ const MembersPage = ({ current }) => {
112
112
  onChanged: load
113
113
  }, `${member.id}/${member.role}/${member.appRoles.join()}`))
114
114
  }) : !error && /* @__PURE__ */ jsxs("div", {
115
- className: "flex items-center gap-2 text-[13px] text-muted-foreground",
115
+ className: "flex items-center gap-2 text-muted-foreground",
116
116
  children: [/* @__PURE__ */ jsx(Spinner, {}), " Loading"]
117
117
  })]
118
118
  })]
@@ -78,7 +78,7 @@ const HandOver = ({ organization, memberId }) => {
78
78
  "aria-label": "New owner",
79
79
  value: target,
80
80
  onChange: (event) => setTarget(event.target.value),
81
- className: "h-8 flex-1 rounded-md border border-border-strong bg-background px-2 text-[13px]",
81
+ className: "h-8 flex-1 rounded-md border border-border-strong bg-background px-2",
82
82
  children: [/* @__PURE__ */ jsx("option", {
83
83
  value: "",
84
84
  children: "Choose a member"
@@ -19,7 +19,7 @@ const OrganizationRow = ({ organization, current, busy, onOpen }) => /* @__PURE_
19
19
  children: [/* @__PURE__ */ jsxs("span", {
20
20
  className: "min-w-0 flex-1",
21
21
  children: [/* @__PURE__ */ jsx("span", {
22
- className: "block truncate text-[13px] font-medium",
22
+ className: "block truncate font-medium",
23
23
  children: organization.name
24
24
  }), /* @__PURE__ */ jsx("span", {
25
25
  className: "block text-xs text-muted-foreground",
@@ -133,7 +133,7 @@ const YourInvitations = () => {
133
133
  children: /* @__PURE__ */ jsx("ul", {
134
134
  className: "divide-y rounded-md border",
135
135
  children: invitations.map((invitation) => /* @__PURE__ */ jsxs("li", {
136
- className: "px-3 py-2.5 text-[13px]",
136
+ className: "px-3 py-2.5",
137
137
  children: [
138
138
  /* @__PURE__ */ jsx("span", {
139
139
  className: "font-medium",
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.19.4";
2
+ var version = "0.19.6";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };
@@ -30,7 +30,7 @@ const BeforeSignIn = ({ children }) => {
30
30
  ]);
31
31
  if (state.kind !== "signed-out" && state.kind !== "visitor") return children;
32
32
  if (!settled) return /* @__PURE__ */ jsxs("div", {
33
- className: "flex h-full min-h-48 items-center justify-center gap-2 text-[13px] text-muted-foreground",
33
+ className: "flex h-full min-h-48 items-center justify-center gap-2 text-muted-foreground",
34
34
  children: [/* @__PURE__ */ jsx(Spinner, {}), " Loading"]
35
35
  });
36
36
  if (!Around) return children;
@@ -20,7 +20,7 @@ const PluginGate = ({ ui, children }) => {
20
20
  const { loads, settled } = usePluginLoads(plugins, usePluginSource());
21
21
  const merged = useMemo(() => mergePlugins(ui, loads), [ui, loads]);
22
22
  if (!settled) return /* @__PURE__ */ jsxs("div", {
23
- className: "flex h-full min-h-48 items-center justify-center gap-2 text-[13px] text-muted-foreground",
23
+ className: "flex h-full min-h-48 items-center justify-center gap-2 text-muted-foreground",
24
24
  children: [/* @__PURE__ */ jsx(Spinner, {}), " Loading"]
25
25
  });
26
26
  return /* @__PURE__ */ jsx(ProjectUiContext.Provider, {
@@ -9,7 +9,7 @@ const PluginNotice = ({ off }) => {
9
9
  return /* @__PURE__ */ jsxs("div", {
10
10
  role: "region",
11
11
  "aria-label": "Plugins off",
12
- className: "flex items-start gap-3 border-b border-warning/40 bg-warning-soft px-4 py-2 text-[13px] text-warning-text",
12
+ className: "flex items-start gap-3 border-b border-warning/40 bg-warning-soft px-4 py-2 text-warning-text",
13
13
  children: [
14
14
  /* @__PURE__ */ jsx(Puzzle, {
15
15
  "aria-hidden": true,
@@ -10,7 +10,7 @@ const variants = {
10
10
  };
11
11
  const sizes = {
12
12
  sm: "h-7 px-2.5 text-xs gap-1.5",
13
- md: "h-8 px-3 text-[13px] gap-2"
13
+ md: "h-8 px-3 text-body gap-2"
14
14
  };
15
15
  /** A button's classes, for a link that looks like one. */
16
16
  const buttonClass = ({ variant = "secondary", size = "md" } = {}) => cn("inline-flex shrink-0 items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:opacity-60 disabled:cursor-default", variants[variant], sizes[size]);
@@ -19,11 +19,11 @@ const Dialog = ({ title, description, onClose, actions, children, wide = false }
19
19
  className: cn("outline-none max-h-[90vh] w-full overflow-y-auto rounded-lg border bg-background p-4 shadow-pop sm:p-5", wide ? "max-w-3xl" : "max-w-md"),
20
20
  children: [
21
21
  /* @__PURE__ */ jsx("h2", {
22
- className: "text-[15px] font-semibold",
22
+ className: "text-[16px] font-semibold",
23
23
  children: title
24
24
  }),
25
25
  description && /* @__PURE__ */ jsx("p", {
26
- className: "mt-1.5 text-[13px] text-muted-foreground",
26
+ className: "mt-1.5 text-muted-foreground",
27
27
  children: description
28
28
  }),
29
29
  children && /* @__PURE__ */ jsx("div", {
@@ -6,7 +6,7 @@ const Input = ({ leading, trailing, invalid, className, wrapperClassName, ...res
6
6
  children: [
7
7
  leading,
8
8
  /* @__PURE__ */ jsx("input", {
9
- className: cn("min-w-0 flex-1 bg-transparent text-[13px] text-foreground outline-none placeholder:text-faint-foreground", className),
9
+ className: cn("min-w-0 flex-1 bg-transparent text-body text-foreground outline-none placeholder:text-faint-foreground", className),
10
10
  ...rest
11
11
  }),
12
12
  trailing