@jxsuite/studio 0.37.1 → 1.1.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 (113) hide show
  1. package/dist/iframe-entry.js +622 -49
  2. package/dist/iframe-entry.js.map +15 -12
  3. package/dist/studio.css +1333 -0
  4. package/dist/studio.js +103709 -81001
  5. package/dist/studio.js.map +201 -157
  6. package/package.json +47 -45
  7. package/src/account-status.ts +39 -0
  8. package/src/browse/browse.ts +10 -14
  9. package/src/canvas/canvas-live-render.ts +21 -2
  10. package/src/canvas/canvas-render.ts +35 -16
  11. package/src/canvas/canvas-utils.ts +118 -72
  12. package/src/canvas/iframe-entry.ts +20 -0
  13. package/src/canvas/iframe-eval.ts +155 -0
  14. package/src/canvas/iframe-host.ts +132 -6
  15. package/src/canvas/iframe-inline-edit.ts +107 -1
  16. package/src/canvas/iframe-protocol.ts +43 -3
  17. package/src/canvas/iframe-render.ts +18 -0
  18. package/src/collab/collab-session.ts +23 -8
  19. package/src/collab/collab-state.ts +6 -3
  20. package/src/component-props.ts +104 -0
  21. package/src/editor/context-menu.ts +1 -1
  22. package/src/editor/inline-edit-apply.ts +36 -1
  23. package/src/editor/inline-edit.ts +58 -1
  24. package/src/editor/repeater-scope.ts +8 -13
  25. package/src/editor/shortcuts.ts +41 -12
  26. package/src/files/file-ops.ts +14 -0
  27. package/src/files/files.ts +56 -1
  28. package/src/format/format-host.ts +63 -1
  29. package/src/grid/cell-editors.ts +288 -0
  30. package/src/grid/cell-popovers.ts +108 -0
  31. package/src/grid/csv-codec.ts +122 -0
  32. package/src/grid/edit-buffer.ts +490 -0
  33. package/src/grid/grid-controller.ts +417 -0
  34. package/src/grid/grid-layout.ts +83 -0
  35. package/src/grid/grid-open.ts +167 -0
  36. package/src/grid/grid-panel.ts +302 -0
  37. package/src/grid/grid-source.ts +210 -0
  38. package/src/grid/grid-view.ts +367 -0
  39. package/src/grid/schema-columns.ts +261 -0
  40. package/src/grid/sources/connector-source.ts +217 -0
  41. package/src/grid/sources/content-source.ts +542 -0
  42. package/src/grid/sources/csv-file-source.ts +247 -0
  43. package/src/grid/tabulator-tables.d.ts +120 -0
  44. package/src/new-project/add-repo-modal.ts +183 -0
  45. package/src/new-project/new-project-modal.ts +22 -3
  46. package/src/page-params.ts +34 -8
  47. package/src/panels/ai-chat/chat-markdown.ts +2 -2
  48. package/src/panels/ai-panel.ts +61 -4
  49. package/src/panels/block-action-bar.ts +8 -3
  50. package/src/panels/chat-panel.ts +98 -0
  51. package/src/panels/data-grid.ts +241 -0
  52. package/src/panels/editors.ts +43 -17
  53. package/src/panels/events-panel.ts +137 -44
  54. package/src/panels/formula-workspace.ts +379 -0
  55. package/src/panels/frontmatter-fields.ts +231 -0
  56. package/src/panels/frontmatter-panel.ts +132 -0
  57. package/src/panels/git-panel.ts +1 -1
  58. package/src/panels/head-panel.ts +11 -175
  59. package/src/panels/properties-panel.ts +154 -144
  60. package/src/panels/right-panel.ts +9 -47
  61. package/src/panels/signals-panel.ts +217 -460
  62. package/src/panels/statement-editor.ts +710 -0
  63. package/src/panels/statusbar.ts +1 -1
  64. package/src/panels/style-panel.ts +25 -1
  65. package/src/panels/stylebook-panel.ts +0 -2
  66. package/src/panels/tab-bar.ts +175 -6
  67. package/src/panels/tab-strip.ts +62 -6
  68. package/src/panels/toolbar.ts +37 -3
  69. package/src/panels/welcome-screen.ts +50 -0
  70. package/src/platform-errors.ts +30 -0
  71. package/src/platforms/cloud.ts +172 -89
  72. package/src/platforms/devserver.ts +172 -0
  73. package/src/services/ai-project-tools.ts +541 -0
  74. package/src/services/ai-session-store.ts +28 -0
  75. package/src/services/ai-system-prompt.ts +194 -24
  76. package/src/services/ai-tools.ts +88 -21
  77. package/src/services/automation.ts +16 -0
  78. package/src/services/context-resolver.ts +73 -0
  79. package/src/services/data-service.ts +155 -0
  80. package/src/services/document-assistant.ts +81 -11
  81. package/src/services/gated-registry.ts +72 -0
  82. package/src/services/live-preview.ts +0 -0
  83. package/src/services/monaco-setup.ts +75 -26
  84. package/src/services/preview-eval.ts +68 -0
  85. package/src/services/project-adoption.ts +31 -0
  86. package/src/settings/contributed-section.ts +406 -0
  87. package/src/settings/extension-sections.ts +145 -0
  88. package/src/settings/schema-field-ui.ts +4 -2
  89. package/src/settings/settings-modal.ts +101 -42
  90. package/src/site-context.ts +12 -1
  91. package/src/store.ts +4 -0
  92. package/src/studio.ts +107 -52
  93. package/src/tabs/patch-ops.ts +25 -0
  94. package/src/tabs/tab.ts +39 -1
  95. package/src/tabs/transact.ts +61 -14
  96. package/src/types.ts +132 -1
  97. package/src/ui/dynamic-slot.ts +272 -0
  98. package/src/ui/expression-editor.ts +423 -125
  99. package/src/ui/field-row.ts +5 -0
  100. package/src/ui/form-controls.ts +322 -0
  101. package/src/ui/formula-catalog.ts +557 -0
  102. package/src/ui/formula-chips.ts +216 -0
  103. package/src/ui/formula-palette.ts +211 -0
  104. package/src/ui/layers.ts +40 -0
  105. package/src/ui/media-picker.ts +1 -1
  106. package/src/ui/panel-resize.ts +15 -1
  107. package/src/ui/progress-modal.ts +2 -2
  108. package/src/ui/schema-form.ts +524 -0
  109. package/src/utils/preview-format.ts +26 -0
  110. package/src/utils/studio-utils.ts +4 -3
  111. package/src/view.ts +4 -4
  112. package/src/workspace/workspace.ts +14 -0
  113. package/src/settings/content-types-editor.ts +0 -599
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Popover cell editors — media paths and relationship pickers.
3
+ *
4
+ * These kinds bypass Tabulator's in-cell editor session entirely: their pickers render Spectrum
5
+ * overlays OUTSIDE the cell, which Tabulator's range module treats as an outside interaction and
6
+ * blur-cancels. Instead, a dblclick on the cell opens an anchored layer popover that writes
7
+ * straight through the edit buffer via `commit`.
8
+ */
9
+ import { html, nothing } from "lit-html";
10
+ import { renderPopover } from "../ui/layers";
11
+ import { renderMediaPicker } from "../ui/media-picker";
12
+ import { listCollectionEntryIds } from "./sources/content-source";
13
+ import { cellToText } from "./schema-columns";
14
+ import type { GridCellValue, GridColumn } from "./grid-source";
15
+
16
+ /** Whether this column edits through an anchored popover instead of an in-cell editor. */
17
+ export function hasPopoverEditor(column: GridColumn): boolean {
18
+ return column.kind === "image" || column.kind === "reference";
19
+ }
20
+
21
+ /** Target content-type name of a relationship column (`#/content/<name>` on the schema). */
22
+ export function referenceTargetType(column: GridColumn): string | null {
23
+ const ref = (column.schema as { $ref?: string } | undefined)?.$ref;
24
+ if (typeof ref !== "string" || !ref.startsWith("#/content/")) {
25
+ return null;
26
+ }
27
+ return ref.slice("#/content/".length);
28
+ }
29
+
30
+ export interface CellPopoverArgs {
31
+ /** Viewport rect of the cell the popover anchors to. */
32
+ anchor: { left: number; bottom: number };
33
+ column: GridColumn;
34
+ value: GridCellValue;
35
+ /** Write the new value through the edit buffer (called on every pick/commit). */
36
+ commit: (value: GridCellValue) => void;
37
+ }
38
+
39
+ function popoverShell(
40
+ column: GridColumn,
41
+ anchor: CellPopoverArgs["anchor"],
42
+ body: unknown,
43
+ dismiss: () => void,
44
+ ) {
45
+ const left = Math.max(4, Math.min(anchor.left, window.innerWidth - 340));
46
+ const top = Math.max(4, Math.min(anchor.bottom + 2, window.innerHeight - 200));
47
+ return html`<sp-popover
48
+ open
49
+ class="jx-grid-cell-popover"
50
+ style="position:fixed;z-index:10000;left:${left}px;top:${top}px"
51
+ >
52
+ <div class="jx-grid-cell-popover-body">
53
+ <div class="jx-grid-cell-popover-title">${column.title}</div>
54
+ ${body}
55
+ <div class="jx-grid-cell-popover-actions">
56
+ <sp-button size="s" variant="secondary" @click=${dismiss}>Done</sp-button>
57
+ </div>
58
+ </div>
59
+ </sp-popover>`;
60
+ }
61
+
62
+ /**
63
+ * Open the popover editor for an image or reference cell. Resolves target-entry ids up front for
64
+ * relationship columns so the picker renders complete.
65
+ */
66
+ export async function openCellValuePopover(args: CellPopoverArgs): Promise<void> {
67
+ const { anchor, column, commit, value } = args;
68
+ const current = cellToText(value);
69
+
70
+ let body;
71
+ if (column.kind === "reference") {
72
+ const targetType = referenceTargetType(column);
73
+ const ids = targetType ? await listCollectionEntryIds(targetType) : [];
74
+ body = html`
75
+ ${targetType
76
+ ? html`<div class="jx-grid-cell-popover-hint">Entries of “${targetType}”</div>`
77
+ : nothing}
78
+ <select
79
+ class="jx-grid-select"
80
+ @change=${(e: Event) => {
81
+ const picked = (e.target as HTMLSelectElement).value;
82
+ commit(picked === "" ? null : picked);
83
+ }}
84
+ >
85
+ <option value="" ?selected=${current === ""}>—</option>
86
+ ${ids.map((id) => html`<option value=${id} ?selected=${id === current}>${id}</option>`)}
87
+ </select>
88
+ <input
89
+ class="jx-grid-input"
90
+ placeholder="Custom id…"
91
+ .value=${ids.includes(current) ? "" : current}
92
+ @change=${(e: Event) => {
93
+ const text = (e.target as HTMLInputElement).value.trim();
94
+ commit(text === "" ? null : text);
95
+ }}
96
+ />
97
+ `;
98
+ } else {
99
+ body = renderMediaPicker(column.field, current, (val) => {
100
+ commit(val === "" ? null : val);
101
+ });
102
+ }
103
+
104
+ const handle = renderPopover(
105
+ popoverShell(column, anchor, body, () => handle.dismiss()),
106
+ { dismissOnOutsideClick: true },
107
+ );
108
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Positional CSV codec for the grid editor.
3
+ *
4
+ * RFC 4180 parse/serialize that keeps what the parser extension's object-row loader discards:
5
+ * column order, duplicate/empty headers, untrimmed whitespace, the file's dominant EOL, and whether
6
+ * it ended with a newline. Values stay raw strings — typing lives in schema-columns. Serialization
7
+ * is canonical (quotes only where required), so an edited file is re-emitted minimally quoted;
8
+ * untouched files are never rewritten.
9
+ */
10
+
11
+ export interface CsvDocument {
12
+ headers: string[];
13
+ rows: string[][];
14
+ eol: "\n" | "\r\n";
15
+ trailingNewline: boolean;
16
+ }
17
+
18
+ /** Split CSV text into records of raw fields, honoring quotes across commas and newlines. */
19
+ function splitRecords(text: string): string[][] {
20
+ const records: string[][] = [];
21
+ let fields: string[] = [];
22
+ let field = "";
23
+ let inQuotes = false;
24
+ let fieldStarted = false;
25
+
26
+ const endField = () => {
27
+ fields.push(field);
28
+ field = "";
29
+ fieldStarted = false;
30
+ };
31
+ const endRecord = () => {
32
+ endField();
33
+ // A completely empty line is structure, not data — skip it (common trailing-blank case).
34
+ if (fields.length === 1 && fields[0] === "") {
35
+ fields = [];
36
+ return;
37
+ }
38
+ records.push(fields);
39
+ fields = [];
40
+ };
41
+
42
+ for (let i = 0; i < text.length; i++) {
43
+ const ch = text[i];
44
+ if (inQuotes) {
45
+ if (ch === '"') {
46
+ if (text[i + 1] === '"') {
47
+ field += '"';
48
+ i += 1;
49
+ } else {
50
+ inQuotes = false;
51
+ }
52
+ } else {
53
+ field += ch;
54
+ }
55
+ continue;
56
+ }
57
+ if (ch === '"' && !fieldStarted) {
58
+ inQuotes = true;
59
+ fieldStarted = true;
60
+ } else if (ch === '"') {
61
+ // Quote inside an unquoted field — keep it literal (lenient, matches common parsers).
62
+ field += ch;
63
+ } else if (ch === ",") {
64
+ endField();
65
+ } else if (ch === "\n") {
66
+ endRecord();
67
+ } else if (ch === "\r") {
68
+ if (text[i + 1] === "\n") {
69
+ i += 1;
70
+ }
71
+ endRecord();
72
+ } else {
73
+ field += ch;
74
+ fieldStarted = true;
75
+ }
76
+ }
77
+ if (field !== "" || fields.length > 0) {
78
+ endRecord();
79
+ }
80
+ return records;
81
+ }
82
+
83
+ /**
84
+ * Parse CSV text into headers + positional rows. Rows are padded to the header width; when a data
85
+ * row is wider than the header row, headers are padded with "" so no column is lost.
86
+ */
87
+ export function parseCsv(text: string): CsvDocument {
88
+ const crlf = (text.match(/\r\n/g) ?? []).length;
89
+ const bareLf = (text.match(/(?<!\r)\n/g) ?? []).length;
90
+ const eol: CsvDocument["eol"] = crlf > bareLf ? "\r\n" : "\n";
91
+ const trailingNewline = text.endsWith("\n");
92
+
93
+ const records = splitRecords(text);
94
+ const headers = records[0] ?? [];
95
+ const rows = records.slice(1);
96
+ const width = Math.max(headers.length, ...rows.map((r) => r.length), 0);
97
+ while (headers.length < width) {
98
+ headers.push("");
99
+ }
100
+ for (const row of rows) {
101
+ while (row.length < width) {
102
+ row.push("");
103
+ }
104
+ }
105
+ return { eol, headers, rows, trailingNewline };
106
+ }
107
+
108
+ /** Quote a field iff it contains a delimiter, quote, or newline; double embedded quotes. */
109
+ function encodeField(value: string): string {
110
+ if (/[",\n\r]/.test(value)) {
111
+ return `"${value.replaceAll('"', '""')}"`;
112
+ }
113
+ return value;
114
+ }
115
+
116
+ /** Serialize back to CSV text, preserving the document's EOL flavor and trailing-newline flag. */
117
+ export function serializeCsv(doc: CsvDocument): string {
118
+ const lines = [doc.headers, ...doc.rows].map((fields) =>
119
+ fields.map((field) => encodeField(field)).join(","),
120
+ );
121
+ return lines.join(doc.eol) + (doc.trailingNewline ? doc.eol : "");
122
+ }