@backmatter/studio 0.0.16 → 0.0.18

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 (45) hide show
  1. package/dist/chunks/{UiKitApp-_EDqWID3.js → UiKitApp-DkuQBiQZ.js} +2 -2
  2. package/dist/chunks/{WorkspaceRoutes-B2PrFgpz.js → WorkspaceRoutes-DVvb3N-V.js} +27489 -26622
  3. package/dist/chunks/{dashboard-BnlnMnnr.js → dashboard-B4FITg6c.js} +2 -2
  4. package/dist/chunks/{define-studio-BYtYDGLX.js → define-studio-DlYjgrwr.js} +12 -16
  5. package/dist/chunks/{index-D_BHF_8q.js → index-CegtXnEI.js} +1 -1
  6. package/dist/dashboard.js +2 -2
  7. package/dist/define.js +1 -1
  8. package/dist/index.js +4 -4
  9. package/dist/site-widget.css +45 -1
  10. package/dist/site-widget.js +2799 -2791
  11. package/dist/studio.css +113 -15
  12. package/package.json +2 -2
  13. package/src/api.ts +22 -0
  14. package/src/chat/ChatProvider.tsx +6 -0
  15. package/src/chat/Composer.tsx +3 -1
  16. package/src/chrome/Toast.tsx +115 -0
  17. package/src/chrome/WorkspaceStatusBar.tsx +12 -3
  18. package/src/chrome/commit-error.ts +13 -0
  19. package/src/chrome/pull-failure.ts +61 -0
  20. package/src/{pages/code-session-routes.ts → content-routes.ts} +6 -6
  21. package/src/fields/FieldControl.tsx +771 -0
  22. package/src/fields/LinkFieldInput.tsx +315 -0
  23. package/src/fields/choose-field-input.ts +69 -0
  24. package/src/fields/link-targets.ts +60 -0
  25. package/src/fields/load-relation-documents.ts +35 -0
  26. package/src/fields/omit-field.ts +12 -0
  27. package/src/fields/relation-targets.ts +44 -0
  28. package/src/fields/resolve-object-fields.ts +21 -0
  29. package/src/host-path.ts +13 -0
  30. package/src/pages/BlockSettingsDialog.tsx +19 -115
  31. package/src/pages/BlocksList.tsx +43 -35
  32. package/src/pages/BodyProse.tsx +16 -1
  33. package/src/pages/CodeSessionPage.tsx +186 -248
  34. package/src/pages/DocEditorPage.tsx +168 -213
  35. package/src/pages/MediaFieldInput.tsx +609 -26
  36. package/src/pages/MediaGalleryPage.tsx +14 -3
  37. package/src/pages/PreviewAddress.tsx +263 -0
  38. package/src/pages/RenderedBody.tsx +17 -2
  39. package/src/pages/branch-notice.ts +16 -0
  40. package/src/pages/code-session-boot.ts +0 -12
  41. package/src/pages/code-session-context.tsx +2 -0
  42. package/src/pages/media-selection.ts +79 -0
  43. package/src/shell-resets.css +15 -0
  44. package/src/ui/Input.tsx +178 -20
  45. package/src/ui/popup.ts +11 -2
package/src/ui/Input.tsx CHANGED
@@ -1,5 +1,5 @@
1
1
  // packages/studio/src/ui/Input.tsx
2
- import type { ComponentPropsWithoutRef } from "react";
2
+ import { useState, type ComponentPropsWithoutRef } from "react";
3
3
  import { Input as BaseInput } from "@base-ui/react/input";
4
4
  import { Select as BaseSelect } from "@base-ui/react/select";
5
5
  import * as stylex from "@stylexjs/stylex";
@@ -9,7 +9,7 @@ import { cx, popup, popupClassName } from "./popup";
9
9
  type Props = Omit<ComponentPropsWithoutRef<"input">, "className" | "style">;
10
10
 
11
11
  export function Input(props: Props) {
12
- return <BaseInput {...props} {...stylex.props(styles.input)} />;
12
+ return <BaseInput {...stylex.props(styles.input, styles.inputFocus)} {...props} />;
13
13
  }
14
14
 
15
15
  export type SelectOption = { value: string; label: string; disabled?: boolean };
@@ -22,6 +22,9 @@ export function Select({
22
22
  placeholder = "Select…",
23
23
  name,
24
24
  disabled,
25
+ chip = false,
26
+ clearable = false,
27
+ searchable = false,
25
28
  "aria-label": ariaLabel,
26
29
  id,
27
30
  }: {
@@ -31,34 +34,100 @@ export function Select({
31
34
  placeholder?: string;
32
35
  name?: string;
33
36
  disabled?: boolean;
37
+ /** Selected value sits in the field as a removable chip. */
38
+ chip?: boolean;
39
+ /** Chip × and the field clear control clear the value. Ignored when `chip` is false. */
40
+ clearable?: boolean;
41
+ /** Filter the menu by label or value. The closed field stays a select. */
42
+ searchable?: boolean;
34
43
  "aria-label"?: string;
35
44
  id?: string;
36
45
  }) {
46
+ const selected = options.find((opt) => opt.value === value) ?? null;
47
+ const [search, setSearch] = useState("");
48
+ const query = search.trim().toLowerCase();
49
+ const visible = query
50
+ ? options.filter(
51
+ (opt) =>
52
+ opt.label.toLowerCase().includes(query) || opt.value.toLowerCase().includes(query),
53
+ )
54
+ : options;
55
+ const clear = (event: { preventDefault: () => void; stopPropagation: () => void }) => {
56
+ event.preventDefault();
57
+ event.stopPropagation();
58
+ onValueChange(null);
59
+ };
37
60
  return (
38
61
  <BaseSelect.Root
39
62
  items={options}
40
63
  value={value}
41
64
  onValueChange={(next) => onValueChange(typeof next === "string" ? next : null)}
65
+ onOpenChange={(open) => {
66
+ if (!open) setSearch("");
67
+ }}
42
68
  name={name}
43
69
  disabled={disabled}
44
70
  >
45
71
  <BaseSelect.Trigger
46
72
  id={id}
47
73
  aria-label={ariaLabel}
74
+ data-no-ring=""
48
75
  className={cx(styles.input, styles.selectTrigger)}
49
76
  >
50
- <BaseSelect.Value placeholder={placeholder} className={cx(styles.selectValue)} />
51
- <BaseSelect.Icon className={cx(styles.selectIcon)}>
52
- <svg width="12" height="12" viewBox="0 0 16 16" fill="none" aria-hidden>
53
- <path
54
- d="M4.5 6.25 8 9.75l3.5-3.5"
55
- stroke="currentColor"
56
- strokeWidth="1.5"
57
- strokeLinecap="round"
58
- strokeLinejoin="round"
59
- />
60
- </svg>
61
- </BaseSelect.Icon>
77
+ {chip && selected ? (
78
+ <span {...stylex.props(styles.chip)}>
79
+ <span {...stylex.props(styles.chipLabel)}>{selected.label}</span>
80
+ {clearable && !disabled ? (
81
+ <span
82
+ data-select-clear
83
+ aria-hidden
84
+ {...stylex.props(styles.chipRemove)}
85
+ onPointerDown={(event) => event.stopPropagation()}
86
+ onClick={clear}
87
+ >
88
+ ×
89
+ </span>
90
+ ) : null}
91
+ </span>
92
+ ) : (
93
+ <BaseSelect.Value
94
+ placeholder={placeholder}
95
+ className={cx(styles.selectValue, chip && styles.placeholder)}
96
+ />
97
+ )}
98
+ <span {...stylex.props(styles.selectActions)}>
99
+ {chip && clearable && selected && !disabled ? (
100
+ <span
101
+ data-select-clear
102
+ role="button"
103
+ aria-label="Clear"
104
+ {...stylex.props(styles.fieldClear)}
105
+ onPointerDown={(event) => event.stopPropagation()}
106
+ onClick={clear}
107
+ >
108
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="none" aria-hidden>
109
+ <circle cx="8" cy="8" r="6.25" stroke="currentColor" strokeWidth="1.25" />
110
+ <path
111
+ d="M6 6l4 4M10 6l-4 4"
112
+ stroke="currentColor"
113
+ strokeWidth="1.25"
114
+ strokeLinecap="round"
115
+ />
116
+ </svg>
117
+ </span>
118
+ ) : null}
119
+ <BaseSelect.Icon className={cx(styles.selectIcon)}>
120
+ <svg width="12" height="12" viewBox="0 0 16 16" fill="none" aria-hidden>
121
+ <path
122
+ d="M4.5 6.25 8 9.75l3.5-3.5"
123
+ stroke="currentColor"
124
+ strokeWidth="1.5"
125
+ strokeLinecap="round"
126
+ strokeLinejoin="round"
127
+ />
128
+ </svg>
129
+ </BaseSelect.Icon>
130
+ </span>
62
131
  </BaseSelect.Trigger>
63
132
  <BaseSelect.Portal>
64
133
  <BaseSelect.Positioner
@@ -69,8 +138,27 @@ export function Select({
69
138
  className={cx(popup.positioner)}
70
139
  >
71
140
  <BaseSelect.Popup className={(state) => popupClassName(state, styles.selectPopup)}>
141
+ {searchable ? (
142
+ <input
143
+ data-select-search
144
+ data-no-ring=""
145
+ value={search}
146
+ placeholder="Search"
147
+ spellCheck={false}
148
+ autoComplete="off"
149
+ className={cx(popup.searchInput)}
150
+ onChange={(event) => setSearch(event.target.value)}
151
+ onMouseDown={(event) => {
152
+ event.preventDefault();
153
+ event.stopPropagation();
154
+ event.currentTarget.focus();
155
+ }}
156
+ onPointerDown={(event) => event.stopPropagation()}
157
+ onKeyDown={(event) => event.stopPropagation()}
158
+ />
159
+ ) : null}
72
160
  <BaseSelect.List>
73
- {options.map((opt) => (
161
+ {visible.map((opt) => (
74
162
  <BaseSelect.Item
75
163
  key={opt.value}
76
164
  value={opt.value}
@@ -115,10 +203,10 @@ export function Textarea({
115
203
  }: Omit<ComponentPropsWithoutRef<"textarea">, "className" | "style"> & {
116
204
  mono?: boolean;
117
205
  }) {
118
- return (
206
+ return (
119
207
  <textarea
120
208
  {...props}
121
- {...stylex.props(styles.input, styles.textarea, mono && styles.mono)}
209
+ {...stylex.props(styles.input, styles.inputFocus, styles.textarea, mono && styles.mono)}
122
210
  />
123
211
  );
124
212
  }
@@ -137,6 +225,11 @@ const styles = stylex.create({
137
225
  color: colors.text,
138
226
  fontSize: type.md,
139
227
  outline: "none",
228
+ ":disabled": {
229
+ opacity: 0.55,
230
+ },
231
+ },
232
+ inputFocus: {
140
233
  ":focus": {
141
234
  borderColor: colors.focus,
142
235
  outlineWidth: 2,
@@ -144,9 +237,6 @@ const styles = stylex.create({
144
237
  outlineColor: colors.focus,
145
238
  outlineOffset: -1,
146
239
  },
147
- ":disabled": {
148
- opacity: 0.55,
149
- },
150
240
  },
151
241
  selectTrigger: {
152
242
  display: "flex",
@@ -156,6 +246,22 @@ const styles = stylex.create({
156
246
  textAlign: "left",
157
247
  fontFamily: type.sans,
158
248
  cursor: "pointer",
249
+ outline: "none",
250
+ boxShadow: "none",
251
+ ":focus": {
252
+ borderColor: colors.border,
253
+ outlineStyle: "none",
254
+ outlineWidth: 0,
255
+ outlineOffset: 0,
256
+ boxShadow: "none",
257
+ },
258
+ ":focus-visible": {
259
+ borderColor: colors.border,
260
+ outlineStyle: "none",
261
+ outlineWidth: 0,
262
+ outlineOffset: 0,
263
+ boxShadow: "none",
264
+ },
159
265
  },
160
266
  selectValue: {
161
267
  minWidth: 0,
@@ -163,6 +269,58 @@ const styles = stylex.create({
163
269
  textOverflow: "ellipsis",
164
270
  whiteSpace: "nowrap",
165
271
  },
272
+ placeholder: {
273
+ color: colors.textMuted,
274
+ },
275
+ chip: {
276
+ display: "inline-flex",
277
+ alignItems: "center",
278
+ gap: space.xs,
279
+ maxWidth: "100%",
280
+ minHeight: "22px",
281
+ paddingInline: space.sm,
282
+ paddingBlock: "1px",
283
+ borderWidth: 1,
284
+ borderStyle: "solid",
285
+ borderColor: colors.border,
286
+ borderRadius: radius.sm,
287
+ backgroundColor: colors.bgHover,
288
+ color: colors.text,
289
+ fontSize: type.sm,
290
+ },
291
+ chipLabel: {
292
+ minWidth: 0,
293
+ overflow: "hidden",
294
+ textOverflow: "ellipsis",
295
+ whiteSpace: "nowrap",
296
+ },
297
+ chipRemove: {
298
+ display: "inline-flex",
299
+ alignItems: "center",
300
+ justifyContent: "center",
301
+ margin: 0,
302
+ padding: 0,
303
+ borderWidth: 0,
304
+ backgroundColor: "transparent",
305
+ color: colors.textMuted,
306
+ fontSize: type.sm,
307
+ lineHeight: 1,
308
+ cursor: "pointer",
309
+ },
310
+ selectActions: {
311
+ display: "inline-flex",
312
+ alignItems: "center",
313
+ gap: space.xs,
314
+ flexShrink: 0,
315
+ marginLeft: "auto",
316
+ },
317
+ fieldClear: {
318
+ display: "inline-flex",
319
+ alignItems: "center",
320
+ justifyContent: "center",
321
+ color: colors.textMuted,
322
+ cursor: "pointer",
323
+ },
166
324
  selectIcon: {
167
325
  display: "inline-flex",
168
326
  flexShrink: 0,
package/src/ui/popup.ts CHANGED
@@ -183,11 +183,20 @@ export const popup = stylex.create({
183
183
  fontFamily: type.sans,
184
184
  fontSize: type.sm,
185
185
  outline: "none",
186
+ boxShadow: "none",
186
187
  ":focus": {
187
- outline: "none",
188
+ outlineStyle: "none",
189
+ outlineWidth: 0,
190
+ outlineOffset: 0,
191
+ boxShadow: "none",
192
+ borderBottomColor: colors.border,
188
193
  },
189
194
  ":focus-visible": {
190
- outline: "none",
195
+ outlineStyle: "none",
196
+ outlineWidth: 0,
197
+ outlineOffset: 0,
198
+ boxShadow: "none",
199
+ borderBottomColor: colors.border,
191
200
  },
192
201
  "::placeholder": {
193
202
  color: colors.textMuted,