@backmatter/studio 0.0.16 → 0.0.17

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 (36) hide show
  1. package/dist/chunks/{UiKitApp-_EDqWID3.js → UiKitApp-D7vllRQE.js} +2 -2
  2. package/dist/chunks/{WorkspaceRoutes-B2PrFgpz.js → WorkspaceRoutes-DH1LD-2Q.js} +26374 -25887
  3. package/dist/chunks/{dashboard-BnlnMnnr.js → dashboard-Dsaeud2x.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-CGMqw2NH.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 +93 -1
  12. package/package.json +2 -2
  13. package/src/api.ts +19 -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/fields/FieldControl.tsx +748 -0
  21. package/src/fields/choose-field-input.ts +64 -0
  22. package/src/fields/load-relation-documents.ts +35 -0
  23. package/src/fields/omit-field.ts +12 -0
  24. package/src/fields/relation-targets.ts +44 -0
  25. package/src/fields/resolve-object-fields.ts +21 -0
  26. package/src/pages/BlockSettingsDialog.tsx +19 -115
  27. package/src/pages/BlocksList.tsx +43 -35
  28. package/src/pages/BodyProse.tsx +16 -1
  29. package/src/pages/CodeSessionPage.tsx +180 -7
  30. package/src/pages/DocEditorPage.tsx +87 -92
  31. package/src/pages/RenderedBody.tsx +17 -2
  32. package/src/pages/branch-notice.ts +16 -0
  33. package/src/pages/code-session-context.tsx +2 -0
  34. package/src/shell-resets.css +15 -0
  35. package/src/ui/Input.tsx +178 -20
  36. package/src/ui/popup.ts +11 -2
@@ -0,0 +1,748 @@
1
+ // packages/studio/src/fields/FieldControl.tsx
2
+ import { useEffect, useState, type ReactNode } from "react";
3
+ import * as stylex from "@stylexjs/stylex";
4
+ import { documentLabel, matchesWhere } from "@backmatter/core";
5
+ import type { SchemaField } from "../api";
6
+ import { resolveFieldInput, useStudioDefinition } from "../define-studio";
7
+ import { colors, radius, space, type } from "../styles/tokens.stylex";
8
+ import { Input, Select, Textarea } from "../ui/Input";
9
+ import { MediaFieldInput } from "../pages/MediaFieldInput";
10
+ import { chooseFieldInput } from "./choose-field-input";
11
+ import { nestedStartsCollapsed, resolveObjectFields } from "./resolve-object-fields";
12
+
13
+ function clamp(n: number, min: number, max: number): number {
14
+ return Math.min(max, Math.max(min, n));
15
+ }
16
+
17
+ function hexColorValue(value: unknown): string {
18
+ if (typeof value === "string" && /^#[0-9a-fA-F]{6}$/.test(value)) {
19
+ return value.toLowerCase();
20
+ }
21
+ return "#000000";
22
+ }
23
+
24
+ function nestFieldName(name: string): string {
25
+ const dot = name.lastIndexOf(".");
26
+ if (dot >= 0) return name.slice(dot + 1);
27
+ return name;
28
+ }
29
+
30
+ function objectRecord(value: unknown): Record<string, unknown> {
31
+ if (value && typeof value === "object" && !Array.isArray(value)) {
32
+ return value as Record<string, unknown>;
33
+ }
34
+ return {};
35
+ }
36
+
37
+ function arrayValue(value: unknown): unknown[] {
38
+ return Array.isArray(value) ? value : [];
39
+ }
40
+
41
+ function jsonFieldText(value: unknown, kind: "object" | "array"): string {
42
+ if (typeof value === "string") return value;
43
+ return JSON.stringify(value ?? (kind === "array" ? [] : {}), null, 2);
44
+ }
45
+
46
+ export function JsonField({
47
+ value,
48
+ kind,
49
+ readOnly,
50
+ onValidChange,
51
+ onDraftChange,
52
+ }: {
53
+ value: unknown;
54
+ kind: "object" | "array";
55
+ readOnly?: boolean;
56
+ onValidChange: (next: unknown) => void;
57
+ onDraftChange?: (text: string) => void;
58
+ }) {
59
+ const serialized = jsonFieldText(value, kind);
60
+ const [draft, setDraft] = useState(serialized);
61
+ const [focused, setFocused] = useState(false);
62
+ const [invalid, setInvalid] = useState(false);
63
+ useEffect(() => {
64
+ if (!focused) {
65
+ setDraft(serialized);
66
+ setInvalid(false);
67
+ }
68
+ }, [serialized, focused]);
69
+ return (
70
+ <Textarea
71
+ value={draft}
72
+ readOnly={readOnly}
73
+ aria-invalid={invalid || undefined}
74
+ onFocus={() => setFocused(true)}
75
+ onBlur={() => setFocused(false)}
76
+ onChange={(e) => {
77
+ const next = e.target.value;
78
+ setDraft(next);
79
+ onDraftChange?.(next);
80
+ try {
81
+ onValidChange(JSON.parse(next || (kind === "array" ? "[]" : "{}")));
82
+ setInvalid(false);
83
+ } catch {
84
+ setInvalid(true);
85
+ }
86
+ }}
87
+ />
88
+ );
89
+ }
90
+
91
+ type ModelRef = { type: string; external?: boolean; fields?: Record<string, SchemaField> };
92
+ type RelationDocument = { slug: string; frontmatter: Record<string, unknown> };
93
+ type RelationDocumentLists = Record<string, RelationDocument[]>;
94
+
95
+ function relationDocumentsForField(
96
+ field: SchemaField,
97
+ documents: RelationDocument[] | undefined,
98
+ documentLists: RelationDocumentLists | undefined,
99
+ ): RelationDocument[] | undefined {
100
+ if (documents !== undefined) return documents;
101
+ if (field.kind === "relation" && field.relationTo && documentLists) {
102
+ return documentLists[field.relationTo];
103
+ }
104
+ return undefined;
105
+ }
106
+
107
+ type SharedFieldProps = {
108
+ name: string;
109
+ field: SchemaField;
110
+ value: unknown;
111
+ models: ModelRef[];
112
+ objects: Array<{ name: string; fields: Record<string, SchemaField> }>;
113
+ documents?: RelationDocument[];
114
+ documentLists?: RelationDocumentLists;
115
+ readOnly?: boolean;
116
+ ancestorObjectNames: string[];
117
+ blocksEditor?: (props: {
118
+ value: unknown;
119
+ readOnly?: boolean;
120
+ onChange: (value: unknown) => void;
121
+ }) => ReactNode;
122
+ onChange: (value: unknown) => void;
123
+ };
124
+
125
+ function RelationFieldInput({
126
+ name,
127
+ field,
128
+ value,
129
+ models,
130
+ documents,
131
+ readOnly,
132
+ onChange,
133
+ }: {
134
+ name: string;
135
+ field: SchemaField;
136
+ value: unknown;
137
+ models: ModelRef[];
138
+ documents?: RelationDocument[];
139
+ readOnly?: boolean;
140
+ onChange: (value: unknown) => void;
141
+ }) {
142
+ const slugValue = value == null || value === "" ? "" : String(value);
143
+ const targetModel = models.find((model) => model.type === field.relationTo);
144
+ const labelFor = (doc: RelationDocument) =>
145
+ documentLabel(doc.frontmatter, targetModel ?? null) ?? doc.slug;
146
+ const offered =
147
+ documents === undefined
148
+ ? []
149
+ : documents.filter((doc) =>
150
+ matchesWhere({ slug: doc.slug, frontmatter: doc.frontmatter }, field.where),
151
+ );
152
+ const options = offered.map((doc) => ({ value: doc.slug, label: labelFor(doc) }));
153
+ if (slugValue && !options.some((option) => option.value === slugValue)) {
154
+ options.unshift({ value: slugValue, label: slugValue });
155
+ }
156
+
157
+ return (
158
+ <Select
159
+ name={name}
160
+ chip
161
+ searchable
162
+ clearable={!field.required}
163
+ placeholder="Select a value"
164
+ options={options}
165
+ value={slugValue || null}
166
+ disabled={readOnly}
167
+ onValueChange={(next) => {
168
+ if (readOnly) return;
169
+ onChange(next == null ? undefined : next);
170
+ }}
171
+ />
172
+ );
173
+ }
174
+
175
+ function CollapsibleNest({
176
+ fieldName,
177
+ objectName,
178
+ ancestorObjectNames,
179
+ children,
180
+ }: {
181
+ fieldName: string;
182
+ objectName: string | undefined;
183
+ ancestorObjectNames: string[];
184
+ readOnly?: boolean;
185
+ children: ReactNode;
186
+ }) {
187
+ const startsCollapsed = nestedStartsCollapsed(ancestorObjectNames, objectName);
188
+ const [expanded, setExpanded] = useState(!startsCollapsed);
189
+ if (!objectName) {
190
+ return <>{children}</>;
191
+ }
192
+ return (
193
+ <div {...stylex.props(styles.nest)}>
194
+ <button
195
+ type="button"
196
+ data-nest-toggle={fieldName}
197
+ {...stylex.props(styles.nestToggle)}
198
+ onClick={() => setExpanded((open) => !open)}
199
+ >
200
+ {expanded ? "Collapse" : "Expand"} {fieldName}
201
+ </button>
202
+ {expanded ? (
203
+ <div data-nest={fieldName} {...stylex.props(styles.nestBody)}>
204
+ {children}
205
+ </div>
206
+ ) : null}
207
+ </div>
208
+ );
209
+ }
210
+
211
+ function ObjectFieldEditor({
212
+ name,
213
+ field,
214
+ value,
215
+ models,
216
+ objects,
217
+ documentLists,
218
+ readOnly,
219
+ ancestorObjectNames,
220
+ blocksEditor,
221
+ onChange,
222
+ }: SharedFieldProps) {
223
+ const resolved = resolveObjectFields(field, objects);
224
+ if (!resolved) {
225
+ return (
226
+ <JsonField
227
+ kind="object"
228
+ value={value}
229
+ readOnly={readOnly}
230
+ onValidChange={onChange}
231
+ />
232
+ );
233
+ }
234
+
235
+ const record = objectRecord(value);
236
+ const objectName = field.object;
237
+ const nextAncestors = objectName ? [...ancestorObjectNames, objectName] : ancestorObjectNames;
238
+
239
+ const body = (
240
+ <div {...stylex.props(styles.objectFields)}>
241
+ {Object.entries(resolved).map(([fieldName, nestedField]) => (
242
+ <FieldControl
243
+ key={fieldName}
244
+ name={`${name}.${fieldName}`}
245
+ field={nestedField}
246
+ value={record[fieldName]}
247
+ models={models}
248
+ objects={objects}
249
+ documentLists={documentLists}
250
+ readOnly={readOnly}
251
+ ancestorObjectNames={nextAncestors}
252
+ blocksEditor={blocksEditor}
253
+ onChange={(next) => {
254
+ onChange({ ...record, [fieldName]: next });
255
+ }}
256
+ />
257
+ ))}
258
+ </div>
259
+ );
260
+
261
+ if (
262
+ !objectName ||
263
+ !nestedStartsCollapsed(ancestorObjectNames, objectName) ||
264
+ /\[\d+\]$/.test(name)
265
+ ) {
266
+ return body;
267
+ }
268
+
269
+ return (
270
+ <CollapsibleNest
271
+ fieldName={nestFieldName(name)}
272
+ objectName={objectName}
273
+ ancestorObjectNames={ancestorObjectNames}
274
+ readOnly={readOnly}
275
+ >
276
+ {body}
277
+ </CollapsibleNest>
278
+ );
279
+ }
280
+
281
+ function ArrayFieldEditor({
282
+ name,
283
+ field,
284
+ value,
285
+ models,
286
+ objects,
287
+ documentLists,
288
+ readOnly,
289
+ ancestorObjectNames,
290
+ blocksEditor,
291
+ onChange,
292
+ }: SharedFieldProps) {
293
+ const resolved = resolveObjectFields(field, objects);
294
+ if (!resolved) {
295
+ return (
296
+ <JsonField
297
+ kind="array"
298
+ value={value}
299
+ readOnly={readOnly}
300
+ onValidChange={onChange}
301
+ />
302
+ );
303
+ }
304
+
305
+ const items = arrayValue(value);
306
+ const itemField: SchemaField = { kind: "object", object: field.object, fields: resolved };
307
+ const objectName = field.object;
308
+ const nextAncestors = objectName ? [...ancestorObjectNames, objectName] : ancestorObjectNames;
309
+
310
+ const list = (
311
+ <div {...stylex.props(styles.arrayList)}>
312
+ {items.map((item, index) => (
313
+ <div key={index} {...stylex.props(styles.arrayItem)}>
314
+ <FieldControl
315
+ name={`${name}[${index}]`}
316
+ field={itemField}
317
+ value={item}
318
+ models={models}
319
+ objects={objects}
320
+ documentLists={documentLists}
321
+ readOnly={readOnly}
322
+ ancestorObjectNames={nextAncestors}
323
+ blocksEditor={blocksEditor}
324
+ onChange={(next) => {
325
+ const nextItems = [...items];
326
+ nextItems[index] = next;
327
+ onChange(nextItems);
328
+ }}
329
+ />
330
+ <div {...stylex.props(styles.arrayActions)}>
331
+ <button
332
+ type="button"
333
+ data-action="array-up"
334
+ disabled={readOnly || index === 0}
335
+ onClick={() => {
336
+ if (index === 0) return;
337
+ const nextItems = [...items];
338
+ const prev = nextItems[index - 1];
339
+ nextItems[index - 1] = nextItems[index]!;
340
+ nextItems[index] = prev!;
341
+ onChange(nextItems);
342
+ }}
343
+ >
344
+ Up
345
+ </button>
346
+ <button
347
+ type="button"
348
+ data-action="array-down"
349
+ disabled={readOnly || index === items.length - 1}
350
+ onClick={() => {
351
+ if (index >= items.length - 1) return;
352
+ const nextItems = [...items];
353
+ const next = nextItems[index + 1];
354
+ nextItems[index + 1] = nextItems[index]!;
355
+ nextItems[index] = next!;
356
+ onChange(nextItems);
357
+ }}
358
+ >
359
+ Down
360
+ </button>
361
+ <button
362
+ type="button"
363
+ data-action="array-remove"
364
+ disabled={readOnly}
365
+ onClick={() => {
366
+ onChange(items.filter((_, i) => i !== index));
367
+ }}
368
+ >
369
+ Remove
370
+ </button>
371
+ </div>
372
+ </div>
373
+ ))}
374
+ <button
375
+ type="button"
376
+ data-action="array-add"
377
+ disabled={readOnly}
378
+ onClick={() => onChange([...items, {}])}
379
+ >
380
+ Add
381
+ </button>
382
+ </div>
383
+ );
384
+
385
+ if (!objectName || !nestedStartsCollapsed(ancestorObjectNames, objectName)) {
386
+ return list;
387
+ }
388
+
389
+ return (
390
+ <CollapsibleNest
391
+ fieldName={nestFieldName(name)}
392
+ objectName={objectName}
393
+ ancestorObjectNames={ancestorObjectNames}
394
+ readOnly={readOnly}
395
+ >
396
+ {list}
397
+ </CollapsibleNest>
398
+ );
399
+ }
400
+
401
+ function commitNumber(raw: string, onChange: (value: unknown) => void): void {
402
+ if (raw.trim() === "") {
403
+ onChange(undefined);
404
+ return;
405
+ }
406
+ const parsed = Number(raw);
407
+ if (!Number.isFinite(parsed)) return;
408
+ onChange(parsed);
409
+ }
410
+
411
+ export function FieldControl({
412
+ name,
413
+ field,
414
+ value,
415
+ models,
416
+ objects = [],
417
+ documents,
418
+ documentLists,
419
+ readOnly,
420
+ ancestorObjectNames = [],
421
+ blocksEditor,
422
+ onChange,
423
+ }: {
424
+ name: string;
425
+ field: SchemaField;
426
+ value: unknown;
427
+ models: ModelRef[];
428
+ objects?: Array<{ name: string; fields: Record<string, SchemaField> }>;
429
+ documents?: RelationDocument[];
430
+ documentLists?: RelationDocumentLists;
431
+ readOnly?: boolean;
432
+ ancestorObjectNames?: string[];
433
+ blocksEditor?: (props: {
434
+ value: unknown;
435
+ readOnly?: boolean;
436
+ onChange: (value: unknown) => void;
437
+ }) => ReactNode;
438
+ onChange: (value: unknown) => void;
439
+ }) {
440
+ const definition = useStudioDefinition();
441
+ const kind = chooseFieldInput(definition, field, models);
442
+
443
+ if (kind === "custom") {
444
+ const CustomInput = resolveFieldInput(definition, field);
445
+ if (CustomInput) {
446
+ return (
447
+ <CustomInput name={name} field={field} value={value} readOnly={readOnly} onChange={onChange} />
448
+ );
449
+ }
450
+ }
451
+
452
+ if (kind === "select" && field.options) {
453
+ const options = field.options.map((opt) => ({ value: opt, label: opt }));
454
+ const selectValue = value == null || value === "" ? null : String(value);
455
+ return (
456
+ <Select
457
+ name={name}
458
+ chip
459
+ clearable={!field.required}
460
+ placeholder="Select a value"
461
+ options={options}
462
+ value={selectValue}
463
+ disabled={readOnly}
464
+ onValueChange={(next) => {
465
+ if (readOnly) return;
466
+ if (next == null) {
467
+ onChange(undefined);
468
+ return;
469
+ }
470
+ onChange(next);
471
+ }}
472
+ />
473
+ );
474
+ }
475
+
476
+ if (kind === "slider" && typeof field.min === "number" && typeof field.max === "number") {
477
+ const min = field.min;
478
+ const max = field.max;
479
+ const step = typeof field.step === "number" ? field.step : "any";
480
+ const numeric = typeof value === "number" ? value : value === "" ? NaN : Number(value);
481
+ const rangeValue = Number.isFinite(numeric) ? clamp(numeric, min, max) : min;
482
+ const textValue = value == null || value === "" ? "" : String(value);
483
+ return (
484
+ <div {...stylex.props(styles.sliderRow)}>
485
+ <input
486
+ type="range"
487
+ min={min}
488
+ max={max}
489
+ step={step}
490
+ value={String(rangeValue)}
491
+ disabled={readOnly}
492
+ aria-valuemin={min}
493
+ aria-valuemax={max}
494
+ aria-valuenow={rangeValue}
495
+ {...stylex.props(styles.range)}
496
+ onChange={(event) => {
497
+ if (readOnly) return;
498
+ onChange(Number(event.target.value));
499
+ }}
500
+ />
501
+ <input
502
+ type="text"
503
+ inputMode="decimal"
504
+ value={textValue}
505
+ readOnly={readOnly}
506
+ disabled={readOnly}
507
+ aria-label={`${name} value`}
508
+ {...stylex.props(styles.sliderText)}
509
+ onChange={(event) => {
510
+ if (readOnly) return;
511
+ commitNumber(event.target.value, onChange);
512
+ }}
513
+ />
514
+ </div>
515
+ );
516
+ }
517
+
518
+ if (kind === "color") {
519
+ const textValue = value == null ? "" : String(value);
520
+ return (
521
+ <div {...stylex.props(styles.colorRow)}>
522
+ <input
523
+ type="color"
524
+ value={hexColorValue(value)}
525
+ disabled={readOnly}
526
+ {...stylex.props(styles.colorPicker)}
527
+ onChange={(event) => {
528
+ if (readOnly) return;
529
+ onChange(event.target.value);
530
+ }}
531
+ />
532
+ <Input
533
+ value={textValue}
534
+ readOnly={readOnly}
535
+ onChange={(event) => {
536
+ if (readOnly) return;
537
+ onChange(event.target.value);
538
+ }}
539
+ />
540
+ </div>
541
+ );
542
+ }
543
+
544
+ if (kind === "media") {
545
+ return <MediaFieldInput name={name} value={value} readOnly={readOnly} onChange={onChange} />;
546
+ }
547
+
548
+ if (kind === "relation") {
549
+ return (
550
+ <RelationFieldInput
551
+ name={name}
552
+ field={field}
553
+ value={value}
554
+ models={models}
555
+ documents={relationDocumentsForField(field, documents, documentLists)}
556
+ readOnly={readOnly}
557
+ onChange={onChange}
558
+ />
559
+ );
560
+ }
561
+
562
+ if (kind === "boolean") {
563
+ return (
564
+ <input
565
+ type="checkbox"
566
+ checked={value === true}
567
+ disabled={readOnly}
568
+ {...stylex.props(styles.check)}
569
+ onChange={(event) => {
570
+ if (readOnly) return;
571
+ onChange(event.target.checked);
572
+ }}
573
+ />
574
+ );
575
+ }
576
+
577
+ if (kind === "number") {
578
+ return (
579
+ <Input
580
+ type="number"
581
+ value={value == null || value === "" ? "" : String(value)}
582
+ readOnly={readOnly}
583
+ onChange={(event) => {
584
+ if (readOnly) return;
585
+ commitNumber(event.target.value, onChange);
586
+ }}
587
+ />
588
+ );
589
+ }
590
+
591
+ if (kind === "blocks") {
592
+ if (blocksEditor) {
593
+ return <>{blocksEditor({ value, readOnly, onChange })}</>;
594
+ }
595
+ return (
596
+ <JsonField kind="array" value={value} readOnly={readOnly} onValidChange={onChange} />
597
+ );
598
+ }
599
+
600
+ if (kind === "object") {
601
+ return (
602
+ <ObjectFieldEditor
603
+ name={name}
604
+ field={field}
605
+ value={value}
606
+ models={models}
607
+ objects={objects}
608
+ documentLists={documentLists}
609
+ readOnly={readOnly}
610
+ ancestorObjectNames={ancestorObjectNames}
611
+ blocksEditor={blocksEditor}
612
+ onChange={onChange}
613
+ />
614
+ );
615
+ }
616
+
617
+ if (kind === "array") {
618
+ return (
619
+ <ArrayFieldEditor
620
+ name={name}
621
+ field={field}
622
+ value={value}
623
+ models={models}
624
+ objects={objects}
625
+ documentLists={documentLists}
626
+ readOnly={readOnly}
627
+ ancestorObjectNames={ancestorObjectNames}
628
+ blocksEditor={blocksEditor}
629
+ onChange={onChange}
630
+ />
631
+ );
632
+ }
633
+
634
+ if ("multiline" in field && field.multiline) {
635
+ return (
636
+ <Textarea
637
+ rows={4}
638
+ value={value == null ? "" : String(value)}
639
+ readOnly={readOnly}
640
+ onChange={(event) => {
641
+ if (readOnly) return;
642
+ onChange(event.target.value);
643
+ }}
644
+ />
645
+ );
646
+ }
647
+
648
+ return (
649
+ <Input
650
+ value={value == null ? "" : String(value)}
651
+ readOnly={readOnly}
652
+ onChange={(event) => {
653
+ if (readOnly) return;
654
+ onChange(event.target.value);
655
+ }}
656
+ />
657
+ );
658
+ }
659
+
660
+ const styles = stylex.create({
661
+ sliderRow: {
662
+ display: "flex",
663
+ alignItems: "center",
664
+ gap: space.md,
665
+ },
666
+ range: {
667
+ flex: 1,
668
+ minWidth: 0,
669
+ height: "16px",
670
+ margin: 0,
671
+ accentColor: colors.text,
672
+ cursor: "pointer",
673
+ },
674
+ sliderText: {
675
+ boxSizing: "border-box",
676
+ width: "4.5rem",
677
+ flexShrink: 0,
678
+ minHeight: "32px",
679
+ paddingInline: space.md,
680
+ paddingBlock: space.sm,
681
+ borderRadius: radius.md,
682
+ borderWidth: 1,
683
+ borderStyle: "solid",
684
+ borderColor: colors.border,
685
+ backgroundColor: colors.bgInput,
686
+ color: colors.text,
687
+ fontSize: type.md,
688
+ fontVariantNumeric: "tabular-nums",
689
+ outline: "none",
690
+ ":focus": {
691
+ borderColor: colors.focus,
692
+ outlineWidth: 2,
693
+ outlineStyle: "solid",
694
+ outlineColor: colors.focus,
695
+ outlineOffset: -1,
696
+ },
697
+ },
698
+ colorRow: {
699
+ display: "flex",
700
+ alignItems: "center",
701
+ gap: space.sm,
702
+ },
703
+ colorPicker: {
704
+ width: "2.5rem",
705
+ height: "2rem",
706
+ padding: 0,
707
+ borderWidth: 0,
708
+ backgroundColor: "transparent",
709
+ cursor: "pointer",
710
+ },
711
+ check: {
712
+ width: "1rem",
713
+ height: "1rem",
714
+ },
715
+ objectFields: {
716
+ display: "flex",
717
+ flexDirection: "column",
718
+ gap: space.sm,
719
+ },
720
+ nest: {
721
+ display: "flex",
722
+ flexDirection: "column",
723
+ gap: space.xs,
724
+ },
725
+ nestToggle: {
726
+ alignSelf: "flex-start",
727
+ },
728
+ nestBody: {
729
+ display: "flex",
730
+ flexDirection: "column",
731
+ gap: space.sm,
732
+ },
733
+ arrayList: {
734
+ display: "flex",
735
+ flexDirection: "column",
736
+ gap: space.md,
737
+ },
738
+ arrayItem: {
739
+ display: "flex",
740
+ flexDirection: "column",
741
+ gap: space.sm,
742
+ },
743
+ arrayActions: {
744
+ display: "flex",
745
+ flexWrap: "wrap",
746
+ gap: space.xs,
747
+ },
748
+ });