@agent-native/core-corpus 0.210.2 → 0.211.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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +41 -2
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +4 -2
- package/corpus/templates/analytics/CHANGELOG.md +18 -0
- package/corpus/templates/analytics/actions/build-data-index.ts +11 -2
- package/corpus/templates/analytics/actions/configure-content-collection.ts +29 -4
- package/corpus/templates/analytics/actions/configure-first-party-events-model.ts +9 -7
- package/corpus/templates/analytics/actions/create-analytics-public-key.ts +4 -0
- package/corpus/templates/analytics/actions/estimate-content-signups-cost.ts +72 -0
- package/corpus/templates/analytics/actions/estimate-query-cost.ts +16 -134
- package/corpus/templates/analytics/actions/list-sigma-workbooks.ts +123 -42
- package/corpus/templates/analytics/actions/migrate-sigma-workbook.ts +20 -17
- package/corpus/templates/analytics/actions/revoke-analytics-public-key.ts +4 -0
- package/corpus/templates/analytics/app/components/dashboard/SigmaMigrationDialog.tsx +246 -680
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/analytics/app/i18n/en-US.ts +54 -55
- package/corpus/templates/analytics/app/pages/DashboardOverview.tsx +0 -1
- package/corpus/templates/analytics/app/pages/DataSources.tsx +56 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/filter-vars.ts +4 -3
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +26 -8
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/interpolate.ts +3 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/panel-cost-notice.ts +172 -0
- package/corpus/templates/analytics/app/pages/data-sources/SearchConsoleDetail.tsx +441 -0
- package/corpus/templates/analytics/changelog/2026-10-15-a-content-dashboard-lists-every-blog-article-with-its-author.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-15-data-sources-has-a-google-search-console-card-that-shows-the.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-16-the-content-dashboard-can-show-signups-per-blog-article.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-19-creating-or-revoking-a-public-analytics-key-now-asks-you-to-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-19-search-sigma-workbooks-by-name-and-open-ones-a-teammate-alre.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-blog-signup-attribution-excludes-signups-before-the-first-vi.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-collapsed-analytics-navigation-starts-at-the-top.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-content-keeps-successful-coverage-dates-and-missing-day-gaps.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-private-github-repositories-can-be-indexed-with-your-connect.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-search-console-preserves-backfill-and-paused-collection.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-search-console-refreshes-historical-coverage-when-properties.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-search-console-setup-keeps-saved-properties-readable-and-sup.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/content-view.json +182 -0
- package/corpus/templates/analytics/server/db/schema.ts +1 -0
- package/corpus/templates/analytics/server/lib/bi-migration/external-dashboards-store.ts +56 -18
- package/corpus/templates/analytics/server/lib/bi-migration/unfinished-import.ts +64 -0
- package/corpus/templates/analytics/server/lib/builtin-dashboard-pass.ts +17 -2
- package/corpus/templates/analytics/server/lib/content-metrics/collect.ts +2 -0
- package/corpus/templates/analytics/server/lib/content-metrics/config.ts +92 -3
- package/corpus/templates/analytics/server/lib/content-metrics/legacy-content-view.json +172 -0
- package/corpus/templates/analytics/server/lib/content-metrics/migration.ts +2 -0
- package/corpus/templates/analytics/server/lib/content-metrics/steps/daily-metrics.ts +8 -3
- package/corpus/templates/analytics/server/lib/content-metrics/steps/search-console.ts +16 -3
- package/corpus/templates/analytics/server/lib/content-metrics/steps/signups.ts +240 -0
- package/corpus/templates/analytics/server/lib/content-metrics/store.ts +5 -0
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +21 -0
- package/corpus/templates/analytics/server/lib/dashboard-panel-verification.ts +300 -5
- package/corpus/templates/analytics/server/lib/dashboard-seeds.ts +2 -0
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +28 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +128 -24
- package/corpus/templates/analytics/server/lib/google-analytics.ts +56 -16
- package/corpus/templates/analytics/server/lib/index-build-github.ts +5 -0
- package/corpus/templates/analytics/server/lib/provider-credentials.ts +47 -2
- package/corpus/templates/analytics/server/lib/query-cost-estimate.ts +184 -5
- package/corpus/templates/analytics/server/plugins/db.ts +9 -1
- package/corpus/templates/analytics/shared/builtin-dashboard-ids.ts +2 -0
- package/corpus/templates/analytics/shared/search-console-property.ts +78 -0
- package/corpus/templates/assets/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/brain/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/calendar/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/calendar/actions/delete-event.ts +1 -3
- package/corpus/templates/calendar/actions/delete-events.ts +1 -2
- package/corpus/templates/calendar/actions/event-action-helpers.ts +0 -4
- package/corpus/templates/calendar/actions/update-event.ts +2 -6
- package/corpus/templates/chat/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/clips/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/clips/CHANGELOG.md +11 -24
- package/corpus/templates/clips/actions/create-recording-upload.ts +5 -1
- package/corpus/templates/clips/app/components/player/chapter-list.tsx +7 -6
- package/corpus/templates/clips/app/components/player/comment-composer.tsx +5 -1
- package/corpus/templates/clips/app/components/player/player-controls.tsx +1 -0
- package/corpus/templates/clips/app/components/player/scrubber.tsx +4 -2
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -3
- package/corpus/templates/clips/app/lib/extension-auth.ts +70 -0
- package/corpus/templates/clips/app/root.tsx +22 -10
- package/corpus/templates/clips/app/routes/_app.notifications.tsx +3 -0
- package/corpus/templates/clips/app/routes/import.tsx +115 -110
- package/corpus/templates/clips/changelog/2026-10-16-agent-uploads-can-use-free-clips-storage.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-19-agents-see-a-clip-s-frames-as-soon-as-it-finishes-processing.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-19-chapter-list-times-now-match-the-player-s-clock-and-each-cha.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-19-editing-a-comment-puts-the-cursor-after-its-text-and-reply-o.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-19-youtube-import-followups.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-20-locally-saved-desktop-recordings-now-appear-in-the-recovery-.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +29 -186
- package/corpus/templates/clips/desktop/src/components/RecordingRecovery.tsx +171 -16
- package/corpus/templates/clips/desktop/src/lib/record-first.ts +9 -32
- package/corpus/templates/clips/desktop/src/lib/recording-recovery.ts +30 -1
- package/corpus/templates/clips/server/lib/agent-frame-request.ts +5 -2
- package/corpus/templates/clips/server/lib/recording-upload-ticket.ts +8 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +2 -0
- package/corpus/templates/content/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/content/CHANGELOG.md +12 -0
- package/corpus/templates/content/actions/_content-database-personal-view.ts +5 -15
- package/corpus/templates/content/actions/_database-navigation.ts +3 -3
- package/corpus/templates/content/actions/_database-utils.ts +10 -22
- package/corpus/templates/content/actions/_database-view-setup.ts +16 -76
- package/corpus/templates/content/actions/create-comment-ai-suggestion.ts +93 -44
- package/corpus/templates/content/actions/export-document.ts +2 -14
- package/corpus/templates/content/actions/suggest-document-edit.ts +5 -7
- package/corpus/templates/content/actions/update-content-database-view.ts +2 -14
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +86 -30
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +64 -28
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +6 -46
- package/corpus/templates/content/app/components/editor/PageDraftRecovery.tsx +72 -7
- package/corpus/templates/content/app/components/editor/TableHoverControls.tsx +292 -103
- package/corpus/templates/content/app/components/editor/ThreadSuggestion.tsx +147 -40
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +108 -11
- package/corpus/templates/content/app/components/editor/database/ContentTable.tsx +2 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +150 -166
- package/corpus/templates/content/app/components/editor/database/database-cell-comments.tsx +515 -0
- package/corpus/templates/content/app/components/editor/database/database-item-comments.tsx +5 -43
- package/corpus/templates/content/app/components/editor/database/filter-sort.ts +8 -783
- package/corpus/templates/content/app/components/editor/live-editor-session.ts +5 -1
- package/corpus/templates/content/app/components/editor/page-draft-journal.ts +61 -1
- package/corpus/templates/content/app/components/editor/property-comments.tsx +35 -4
- package/corpus/templates/content/app/components/editor/property-date-format.ts +45 -0
- package/corpus/templates/content/app/components/editor/thread-suggestions.ts +30 -0
- package/corpus/templates/content/app/components/editor/useDocumentReconcileRecovery.ts +23 -4
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +36 -10
- package/corpus/templates/content/app/global.css +23 -0
- package/corpus/templates/content/app/i18n-data.ts +1 -0
- package/corpus/templates/content/changelog/2026-10-16-opening-a-page-in-a-new-tab-while-another-tab-is-still-savin.md +6 -0
- package/corpus/templates/content/changelog/2026-10-16-typing-steadily-in-one-tab-no-longer-makes-another-open-tab-.md +6 -0
- package/corpus/templates/content/changelog/2026-10-19-database-views-can-filter-the-same-property-more-than-once-n.md +6 -0
- package/corpus/templates/content/changelog/2026-10-19-edits-made-from-a-block-s-menu-or-a-table-s-row-and-column-m.md +6 -0
- package/corpus/templates/content/changelog/2026-10-19-starting-a-comment-puts-the-cursor-in-the-comment-box-ready-.md +6 -0
- package/corpus/templates/content/changelog/2026-10-19-table-row-and-column-options-now-appear-for-the-row-or-colum.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +5 -1
- package/corpus/templates/content/docs/product/capabilities/content.revision.suggestions.md +3 -1
- package/corpus/templates/content/e2e/helpers.ts +10 -0
- package/corpus/templates/content/shared/api.ts +18 -12
- package/corpus/templates/content/shared/database-query.ts +107 -13
- package/corpus/templates/content/shared/suggestion-diff.ts +35 -0
- package/corpus/templates/crm/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/design/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/design/CHANGELOG.md +2 -0
- package/corpus/templates/design/actions/delete-file.ts +1 -4
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +10 -1
- package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +24 -4
- package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +14 -9
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +23 -2
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +29 -0
- package/corpus/templates/design/changelog/2026-10-19-reply-and-edit-on-a-comment-in-the-sidebar-or-on-a-canvas-pi.md +6 -0
- package/corpus/templates/design/changelog/2026-10-20-comments-return-to-the-mode-they-came-from-and-layers-search.md +6 -0
- package/corpus/templates/design/e2e/global-setup.ts +185 -85
- package/corpus/templates/dispatch/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/dispatch/CHANGELOG.md +4 -0
- package/corpus/templates/dispatch/actions/apply-workspace-connection-setup.ts +4 -0
- package/corpus/templates/dispatch/actions/delete-workspace-connection.ts +4 -0
- package/corpus/templates/dispatch/actions/set-workspace-connection-grant.ts +4 -0
- package/corpus/templates/dispatch/actions/upsert-workspace-connection.ts +4 -0
- package/corpus/templates/dispatch/changelog/2026-10-19-vault-secrets-grants-link-tokens-app-access-and-workspace-co.md +6 -0
- package/corpus/templates/factory/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +105 -33
- package/corpus/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +128 -74
- package/corpus/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +39 -11
- package/corpus/templates/forms/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/mail/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/mail/.agents/skills/contacts-and-crm/SKILL.md +22 -17
- package/corpus/templates/mail/AGENTS.md +1 -1
- package/corpus/templates/mail/CHANGELOG.md +14 -0
- package/corpus/templates/mail/actions/provider-api-request.ts +3 -3
- package/corpus/templates/mail/app/components/email/IntegrationsSidebar.tsx +211 -54
- package/corpus/templates/mail/app/hooks/use-inbox-threads.ts +26 -2
- package/corpus/templates/mail/app/i18n/en-US.ts +5 -1
- package/corpus/templates/mail/app/lib/gong-connection.ts +27 -0
- package/corpus/templates/mail/app/lib/thread-cache.ts +110 -9
- package/corpus/templates/mail/app/root.tsx +10 -2
- package/corpus/templates/mail/changelog/2026-10-20-connected-gong-settings-now-open-the-shared-integration-mana.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-gong-connection-details-now-match-the-call-data-available-in.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-improve-gong-setup-with-a-click-to-connect-flow-and-permissi.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-mail-lets-you-retry-when-it-cannot-check-your-gong-access.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-mail-thread-cache-stays-isolated-when-mailbox-scope-changes.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-the-inbox-and-recently-opened-threads-now-open-instantly-fro.md +6 -0
- package/corpus/templates/mail/server/handlers/emails.ts +9 -0
- package/corpus/templates/plan/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/plan/CHANGELOG.md +4 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +173 -33
- package/corpus/templates/plan/changelog/2026-10-19-opening-a-comment-thread-puts-the-cursor-in-the-reply-box-an.md +6 -0
- package/corpus/templates/slides/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +6 -42
- package/corpus/templates/slides/app/components/editor/FloatingToolbar.tsx +3 -7
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +5 -14
- package/corpus/templates/slides/app/i18n/en-US.ts +0 -2
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +27 -33
- package/corpus/templates/slides/scripts/edit-fidelity/run.ts +1 -3
- package/corpus/templates/tasks/.agents/skills/actions/references/action-fields.md +5 -3
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
IconTrash,
|
|
12
12
|
} from "@tabler/icons-react";
|
|
13
13
|
import { Fragment, type Node as ProseMirrorNode } from "@tiptap/pm/model";
|
|
14
|
+
import { TextSelection } from "@tiptap/pm/state";
|
|
14
15
|
import { findTable } from "@tiptap/pm/tables";
|
|
15
16
|
import { Editor } from "@tiptap/react";
|
|
16
17
|
import {
|
|
@@ -35,6 +36,8 @@ import {
|
|
|
35
36
|
} from "@/components/ui/tooltip";
|
|
36
37
|
import { cn } from "@/lib/utils";
|
|
37
38
|
|
|
39
|
+
import { LOCAL_FILE_USER_EDIT_META } from "./extensions/LocalMdxComponentNode";
|
|
40
|
+
|
|
38
41
|
interface TableHoverControlsProps {
|
|
39
42
|
editor: Editor;
|
|
40
43
|
}
|
|
@@ -107,13 +110,19 @@ const MENU_COLLISION_PADDING = 8;
|
|
|
107
110
|
|
|
108
111
|
const menuIconClass = "size-4 shrink-0 text-muted-foreground";
|
|
109
112
|
|
|
113
|
+
// A reference preview inside a cell brings its own tables, so these keep only
|
|
114
|
+
// the rows and cells that belong to the given table or row.
|
|
110
115
|
function getRows(table: HTMLElement) {
|
|
111
|
-
return
|
|
116
|
+
return (
|
|
117
|
+
Array.from(table.querySelectorAll("tr")) as HTMLTableRowElement[]
|
|
118
|
+
).filter((row) => row.closest("table") === table);
|
|
112
119
|
}
|
|
113
120
|
|
|
114
121
|
function getCells(row: HTMLTableRowElement | null | undefined) {
|
|
115
122
|
return row
|
|
116
|
-
? (Array.from(row.querySelectorAll("td, th")) as HTMLElement[])
|
|
123
|
+
? (Array.from(row.querySelectorAll("td, th")) as HTMLElement[]).filter(
|
|
124
|
+
(cell) => cell.closest("tr") === row,
|
|
125
|
+
)
|
|
117
126
|
: [];
|
|
118
127
|
}
|
|
119
128
|
|
|
@@ -137,14 +146,24 @@ function getLastRowCell(table: HTMLElement) {
|
|
|
137
146
|
return cells[0] ?? null;
|
|
138
147
|
}
|
|
139
148
|
|
|
149
|
+
// A reference preview renders its own read-only editor, tables included,
|
|
150
|
+
// inside this one. Those tables aren't in this editor's document.
|
|
151
|
+
function isInEditor(root: HTMLElement, element: Element) {
|
|
152
|
+
return element.closest(".ProseMirror") === root;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function getEditorTables(root: HTMLElement) {
|
|
156
|
+
return (Array.from(root.querySelectorAll("table")) as HTMLElement[]).filter(
|
|
157
|
+
(table) => isInEditor(root, table),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
140
161
|
function getCellAddress(editor: Editor, cell: HTMLElement) {
|
|
141
162
|
const table = cell.closest("table") as HTMLElement | null;
|
|
142
163
|
const row = cell.closest("tr") as HTMLTableRowElement | null;
|
|
143
164
|
if (!table || !row) return null;
|
|
144
165
|
|
|
145
|
-
const tables =
|
|
146
|
-
editor.view.dom.querySelectorAll("table"),
|
|
147
|
-
) as HTMLElement[];
|
|
166
|
+
const tables = getEditorTables(editor.view.dom);
|
|
148
167
|
const tableIndex = tables.indexOf(table);
|
|
149
168
|
const rowIndex = getRows(table).indexOf(row);
|
|
150
169
|
const columnIndex = getCells(row).indexOf(cell);
|
|
@@ -154,7 +173,7 @@ function getCellAddress(editor: Editor, cell: HTMLElement) {
|
|
|
154
173
|
return { columnIndex, rowIndex, tableIndex };
|
|
155
174
|
}
|
|
156
175
|
|
|
157
|
-
function getCellAtAddress(
|
|
176
|
+
export function getCellAtAddress(
|
|
158
177
|
tables: HTMLElement[],
|
|
159
178
|
address: SelectedCellAddress | null,
|
|
160
179
|
) {
|
|
@@ -236,6 +255,26 @@ function getChildren(node: ProseMirrorNode) {
|
|
|
236
255
|
return children;
|
|
237
256
|
}
|
|
238
257
|
|
|
258
|
+
// Where a cell starts in a table that starts at tablePos.
|
|
259
|
+
function getCellPos(
|
|
260
|
+
table: ProseMirrorNode,
|
|
261
|
+
tablePos: number,
|
|
262
|
+
address: SelectedCellAddress,
|
|
263
|
+
) {
|
|
264
|
+
const row = table.maybeChild(address.rowIndex);
|
|
265
|
+
if (!row || address.columnIndex >= row.childCount) return null;
|
|
266
|
+
|
|
267
|
+
let pos = tablePos + 1;
|
|
268
|
+
for (let index = 0; index < address.rowIndex; index += 1) {
|
|
269
|
+
pos += table.child(index).nodeSize;
|
|
270
|
+
}
|
|
271
|
+
pos += 1;
|
|
272
|
+
for (let index = 0; index < address.columnIndex; index += 1) {
|
|
273
|
+
pos += row.child(index).nodeSize;
|
|
274
|
+
}
|
|
275
|
+
return pos;
|
|
276
|
+
}
|
|
277
|
+
|
|
239
278
|
function findTableNodeByIndex(
|
|
240
279
|
doc: ProseMirrorNode,
|
|
241
280
|
tableIndex: number,
|
|
@@ -297,6 +336,18 @@ function hasHeaderColumnAtAddress(
|
|
|
297
336
|
);
|
|
298
337
|
}
|
|
299
338
|
|
|
339
|
+
// The menus and edge controls act while they, not the editor, hold focus, and
|
|
340
|
+
// Content saves an unfocused change only when it is marked as the user's.
|
|
341
|
+
function userEditChain(editor: Editor) {
|
|
342
|
+
return editor
|
|
343
|
+
.chain()
|
|
344
|
+
.focus()
|
|
345
|
+
.command(({ tr }) => {
|
|
346
|
+
tr.setMeta(LOCAL_FILE_USER_EDIT_META, true);
|
|
347
|
+
return true;
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
300
351
|
function getTargetElement(target: EventTarget | null) {
|
|
301
352
|
if (target instanceof Element) return target;
|
|
302
353
|
if (target instanceof Node) return target.parentElement;
|
|
@@ -325,7 +376,7 @@ function getHoveredTableEdges(editor: Editor, target: EventTarget | null) {
|
|
|
325
376
|
}
|
|
326
377
|
|
|
327
378
|
const cell = targetElement.closest("td, th") as HTMLElement | null;
|
|
328
|
-
if (!cell || !view.dom
|
|
379
|
+
if (!cell || !isInEditor(view.dom, cell)) {
|
|
329
380
|
return { columnTable: null, rowTable: null };
|
|
330
381
|
}
|
|
331
382
|
|
|
@@ -337,7 +388,11 @@ function getHoveredTableEdges(editor: Editor, target: EventTarget | null) {
|
|
|
337
388
|
|
|
338
389
|
const rows = getRows(table);
|
|
339
390
|
const rowCells = getCells(row);
|
|
340
|
-
|
|
391
|
+
// A table wider than its scrollport keeps its add-column control at the
|
|
392
|
+
// scrollport's edge, beside the column the edge cuts through.
|
|
393
|
+
const isLastColumn =
|
|
394
|
+
rowCells[rowCells.length - 1] === cell ||
|
|
395
|
+
cell.getBoundingClientRect().right > getTableScrollport(table).right + 1;
|
|
341
396
|
const isLastRow = rows[rows.length - 1] === row;
|
|
342
397
|
|
|
343
398
|
return {
|
|
@@ -394,8 +449,11 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
394
449
|
});
|
|
395
450
|
const pointerResizeRef = useRef<PointerResizeState | null>(null);
|
|
396
451
|
const tablesRef = useRef<HTMLElement[]>([]);
|
|
452
|
+
const hoveredCellRef = useRef<HTMLElement | null>(null);
|
|
453
|
+
const menuDismissedOutsideRef = useRef(false);
|
|
397
454
|
const [tables, setTables] = useState<HTMLElement[]>([]);
|
|
398
455
|
const [selectedCell, setSelectedCell] = useState<HTMLElement | null>(null);
|
|
456
|
+
const [hoveredCell, setHoveredCell] = useState<HTMLElement | null>(null);
|
|
399
457
|
const [hoveredEdges, setHoveredEdges] = useState<HoveredTableEdges>({
|
|
400
458
|
columnTable: null,
|
|
401
459
|
rowTable: null,
|
|
@@ -418,6 +476,20 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
418
476
|
setRowMenuOpen(false);
|
|
419
477
|
};
|
|
420
478
|
|
|
479
|
+
// Radix returns focus to the menu's trigger, which strands the keyboard
|
|
480
|
+
// outside the editor: Ctrl+Z there runs the browser's own undo on cell text.
|
|
481
|
+
// The menus aren't modal, so a click elsewhere both closes them and lands;
|
|
482
|
+
// refocusing the editor after it would put back the cell the menu acted on.
|
|
483
|
+
const returnFocusToEditor = (event: Event) => {
|
|
484
|
+
event.preventDefault();
|
|
485
|
+
const dismissedOutside = menuDismissedOutsideRef.current;
|
|
486
|
+
menuDismissedOutsideRef.current = false;
|
|
487
|
+
if (!dismissedOutside) getMountedEditorView(editor)?.focus();
|
|
488
|
+
};
|
|
489
|
+
const noteMenuDismissedOutside = () => {
|
|
490
|
+
menuDismissedOutsideRef.current = true;
|
|
491
|
+
};
|
|
492
|
+
|
|
421
493
|
const clearSelectedCell = () => {
|
|
422
494
|
closeTableMenus();
|
|
423
495
|
selectedCellRef.current = null;
|
|
@@ -442,9 +514,7 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
442
514
|
return;
|
|
443
515
|
}
|
|
444
516
|
|
|
445
|
-
const liveTables =
|
|
446
|
-
view.dom.querySelectorAll("table"),
|
|
447
|
-
) as HTMLElement[];
|
|
517
|
+
const liveTables = getEditorTables(view.dom);
|
|
448
518
|
const nextSelectedCell =
|
|
449
519
|
(selectedCellRef.current?.isConnected
|
|
450
520
|
? selectedCellRef.current
|
|
@@ -510,7 +580,22 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
510
580
|
setHoveredEdges(nextHoveredEdges);
|
|
511
581
|
};
|
|
512
582
|
|
|
513
|
-
|
|
583
|
+
// The row and column options follow the hovered cell, so the pointer keeps
|
|
584
|
+
// it while crossing onto them or into their menu.
|
|
585
|
+
const updateHoveredCell = (target: EventTarget | null) => {
|
|
586
|
+
const element = getTargetElement(target);
|
|
587
|
+
if (element?.closest(".content-table-axis-trigger, [role='menu']")) return;
|
|
588
|
+
|
|
589
|
+
const cell = element?.closest("td, th") as HTMLElement | null;
|
|
590
|
+
const root = getMountedEditorView(editor)?.dom;
|
|
591
|
+
const nextCell = cell && root && isInEditor(root, cell) ? cell : null;
|
|
592
|
+
if (hoveredCellRef.current === nextCell) return;
|
|
593
|
+
|
|
594
|
+
hoveredCellRef.current = nextCell;
|
|
595
|
+
setHoveredCell(nextCell);
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
const selectCellForCommand = (cell: HTMLElement | null, focus = true) => {
|
|
514
599
|
if (!cell || editor.isDestroyed) return false;
|
|
515
600
|
|
|
516
601
|
const view = getMountedEditorView(editor);
|
|
@@ -519,9 +604,7 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
519
604
|
const pos = view.posAtDOM(cell, 0);
|
|
520
605
|
if (pos < 0) return false;
|
|
521
606
|
|
|
522
|
-
editor
|
|
523
|
-
.chain()
|
|
524
|
-
.focus()
|
|
607
|
+
(focus ? editor.chain().focus() : editor.chain())
|
|
525
608
|
.setTextSelection(Math.min(pos + 1, editor.state.doc.content.size))
|
|
526
609
|
.run();
|
|
527
610
|
|
|
@@ -531,6 +614,16 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
531
614
|
return true;
|
|
532
615
|
};
|
|
533
616
|
|
|
617
|
+
// Without focusing the editor: an unfocused editor takes focus a frame later,
|
|
618
|
+
// after the menu opened, and the menu closes when focus leaves it. The menu's
|
|
619
|
+
// commands focus the editor themselves.
|
|
620
|
+
const selectHoveredCell = () => {
|
|
621
|
+
const cell = hoveredCellRef.current;
|
|
622
|
+
if (cell?.isConnected && cell !== selectedCellRef.current) {
|
|
623
|
+
selectCellForCommand(cell, false);
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
|
|
534
627
|
const runAtCell = (cell: HTMLElement | null, action: () => void) => {
|
|
535
628
|
if (!selectCellForCommand(cell)) return;
|
|
536
629
|
action();
|
|
@@ -542,13 +635,18 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
542
635
|
|
|
543
636
|
const addColumnAtEnd = (table: HTMLElement) => {
|
|
544
637
|
runAtCell(getLastColumnCell(table), () => {
|
|
545
|
-
editor
|
|
638
|
+
userEditChain(editor).addColumnAfter().run();
|
|
639
|
+
});
|
|
640
|
+
requestAnimationFrame(() => {
|
|
641
|
+
const scrollport =
|
|
642
|
+
getCellFromEditorSelection(editor)?.closest(".tableWrapper");
|
|
643
|
+
scrollport?.scrollTo({ left: scrollport.scrollWidth });
|
|
546
644
|
});
|
|
547
645
|
};
|
|
548
646
|
|
|
549
647
|
const addRowAtEnd = (table: HTMLElement) => {
|
|
550
648
|
runAtCell(getLastRowCell(table), () => {
|
|
551
|
-
editor
|
|
649
|
+
userEditChain(editor).addRowAfter().run();
|
|
552
650
|
});
|
|
553
651
|
};
|
|
554
652
|
|
|
@@ -572,11 +670,14 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
572
670
|
return { address, table };
|
|
573
671
|
};
|
|
574
672
|
|
|
673
|
+
// The header switches keep their menu open, and the menu isn't modal, so
|
|
674
|
+
// focusing the editor would close it. Closing it focuses the editor instead.
|
|
575
675
|
const replaceTable = (
|
|
576
676
|
tablePos: number,
|
|
577
677
|
oldTable: ProseMirrorNode,
|
|
578
678
|
nextTable: ProseMirrorNode,
|
|
579
679
|
nextAddress: SelectedCellAddress,
|
|
680
|
+
{ keepMenuFocus = false } = {},
|
|
580
681
|
) => {
|
|
581
682
|
const transaction = editor.state.tr.replaceWith(
|
|
582
683
|
tablePos,
|
|
@@ -584,12 +685,22 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
584
685
|
nextTable,
|
|
585
686
|
);
|
|
586
687
|
|
|
688
|
+
// Replacing the table moves the selection out of it, and the controls
|
|
689
|
+
// follow the selected cell: they would unmount, menu and all, until the
|
|
690
|
+
// next frame selects the cell again.
|
|
691
|
+
const cellPos = getCellPos(nextTable, tablePos, nextAddress);
|
|
692
|
+
if (cellPos !== null) {
|
|
693
|
+
transaction.setSelection(
|
|
694
|
+
TextSelection.near(transaction.doc.resolve(cellPos + 1)),
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
transaction.setMeta(LOCAL_FILE_USER_EDIT_META, true);
|
|
587
698
|
editor.view.dispatch(transaction.scrollIntoView());
|
|
588
|
-
editor.view.focus();
|
|
699
|
+
if (!keepMenuFocus) editor.view.focus();
|
|
589
700
|
|
|
590
701
|
requestAnimationFrame(() => {
|
|
591
702
|
const nextCell = getCellAtAddress(
|
|
592
|
-
|
|
703
|
+
getEditorTables(editor.view.dom),
|
|
593
704
|
nextAddress,
|
|
594
705
|
);
|
|
595
706
|
|
|
@@ -598,7 +709,7 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
598
709
|
? getCellAddress(editor, nextCell)
|
|
599
710
|
: nextAddress;
|
|
600
711
|
if (nextCell) {
|
|
601
|
-
selectCellForCommand(nextCell);
|
|
712
|
+
selectCellForCommand(nextCell, !keepMenuFocus);
|
|
602
713
|
} else {
|
|
603
714
|
syncOverlay();
|
|
604
715
|
}
|
|
@@ -735,6 +846,7 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
735
846
|
table.node,
|
|
736
847
|
table.node.copy(Fragment.fromArray(nextRows)),
|
|
737
848
|
address,
|
|
849
|
+
{ keepMenuFocus: true },
|
|
738
850
|
);
|
|
739
851
|
};
|
|
740
852
|
|
|
@@ -776,6 +888,7 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
776
888
|
table.node,
|
|
777
889
|
table.node.copy(Fragment.fromArray(nextRows)),
|
|
778
890
|
address,
|
|
891
|
+
{ keepMenuFocus: true },
|
|
779
892
|
);
|
|
780
893
|
};
|
|
781
894
|
|
|
@@ -785,13 +898,13 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
785
898
|
|
|
786
899
|
while (columnCount < nextCount) {
|
|
787
900
|
if (!selectCellForCommand(getLastColumnCell(table))) break;
|
|
788
|
-
editor
|
|
901
|
+
userEditChain(editor).addColumnAfter().run();
|
|
789
902
|
columnCount += 1;
|
|
790
903
|
}
|
|
791
904
|
|
|
792
905
|
while (columnCount > nextCount) {
|
|
793
906
|
if (!selectCellForCommand(getLastColumnCell(table))) break;
|
|
794
|
-
editor
|
|
907
|
+
userEditChain(editor).deleteColumn().run();
|
|
795
908
|
columnCount -= 1;
|
|
796
909
|
}
|
|
797
910
|
|
|
@@ -804,13 +917,13 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
804
917
|
|
|
805
918
|
while (rowCount < nextCount) {
|
|
806
919
|
if (!selectCellForCommand(getLastRowCell(table))) break;
|
|
807
|
-
editor
|
|
920
|
+
userEditChain(editor).addRowAfter().run();
|
|
808
921
|
rowCount += 1;
|
|
809
922
|
}
|
|
810
923
|
|
|
811
924
|
while (rowCount > nextCount) {
|
|
812
925
|
if (!selectCellForCommand(getLastRowCell(table))) break;
|
|
813
|
-
editor
|
|
926
|
+
userEditChain(editor).deleteRow().run();
|
|
814
927
|
rowCount -= 1;
|
|
815
928
|
}
|
|
816
929
|
|
|
@@ -835,20 +948,22 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
835
948
|
: "content-table-resizing-row",
|
|
836
949
|
);
|
|
837
950
|
|
|
951
|
+
// Count from where the drag started, not from the table's start: the
|
|
952
|
+
// column control can sit at a scrollport edge well short of the table's end.
|
|
838
953
|
if (current.kind === "column") {
|
|
839
954
|
const averageWidth = current.tableRect.width / current.baseColumnCount;
|
|
840
|
-
const target =
|
|
841
|
-
|
|
842
|
-
|
|
955
|
+
const target =
|
|
956
|
+
current.baseColumnCount +
|
|
957
|
+
Math.round((event.clientX - current.startX) / averageWidth);
|
|
843
958
|
if (target !== current.lastTarget) {
|
|
844
959
|
current.lastTarget = target;
|
|
845
960
|
adjustColumnCount(current.table, target);
|
|
846
961
|
}
|
|
847
962
|
} else {
|
|
848
963
|
const averageHeight = current.tableRect.height / current.baseRowCount;
|
|
849
|
-
const target =
|
|
850
|
-
|
|
851
|
-
|
|
964
|
+
const target =
|
|
965
|
+
current.baseRowCount +
|
|
966
|
+
Math.round((event.clientY - current.startY) / averageHeight);
|
|
852
967
|
if (target !== current.lastTarget) {
|
|
853
968
|
current.lastTarget = target;
|
|
854
969
|
adjustRowCount(current.table, target);
|
|
@@ -856,7 +971,7 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
856
971
|
}
|
|
857
972
|
};
|
|
858
973
|
|
|
859
|
-
const
|
|
974
|
+
const endPointerResize = () => {
|
|
860
975
|
const current = pointerResizeRef.current;
|
|
861
976
|
pointerResizeRef.current = null;
|
|
862
977
|
document.documentElement.classList.remove(
|
|
@@ -865,7 +980,19 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
865
980
|
);
|
|
866
981
|
window.removeEventListener("pointermove", handlePointerMove);
|
|
867
982
|
window.removeEventListener("pointerup", handlePointerUp);
|
|
983
|
+
window.removeEventListener("pointercancel", handlePointerCancel);
|
|
984
|
+
return current;
|
|
985
|
+
};
|
|
868
986
|
|
|
987
|
+
// The browser cancels a touch it takes over, e.g. to scroll. That ends the
|
|
988
|
+
// drag without the click's added row or column.
|
|
989
|
+
const handlePointerCancel = () => {
|
|
990
|
+
endPointerResize();
|
|
991
|
+
syncOverlay();
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
const handlePointerUp = () => {
|
|
995
|
+
const current = endPointerResize();
|
|
869
996
|
if (!current) return;
|
|
870
997
|
|
|
871
998
|
if (!current.didResize) {
|
|
@@ -902,6 +1029,7 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
902
1029
|
|
|
903
1030
|
window.addEventListener("pointermove", handlePointerMove);
|
|
904
1031
|
window.addEventListener("pointerup", handlePointerUp);
|
|
1032
|
+
window.addEventListener("pointercancel", handlePointerCancel);
|
|
905
1033
|
};
|
|
906
1034
|
|
|
907
1035
|
useEffect(() => {
|
|
@@ -925,6 +1053,7 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
925
1053
|
if (columnAxisTrigger) {
|
|
926
1054
|
event.preventDefault();
|
|
927
1055
|
event.stopPropagation();
|
|
1056
|
+
selectHoveredCell();
|
|
928
1057
|
holdTableUiSelection();
|
|
929
1058
|
setColumnMenuOpen(true);
|
|
930
1059
|
return;
|
|
@@ -936,6 +1065,7 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
936
1065
|
if (rowAxisTrigger) {
|
|
937
1066
|
event.preventDefault();
|
|
938
1067
|
event.stopPropagation();
|
|
1068
|
+
selectHoveredCell();
|
|
939
1069
|
holdTableUiSelection();
|
|
940
1070
|
setRowMenuOpen(true);
|
|
941
1071
|
return;
|
|
@@ -943,7 +1073,7 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
943
1073
|
|
|
944
1074
|
const cell = target.closest("td, th") as HTMLElement | null;
|
|
945
1075
|
|
|
946
|
-
if (cell && view.dom
|
|
1076
|
+
if (cell && isInEditor(view.dom, cell)) {
|
|
947
1077
|
closeTableMenus();
|
|
948
1078
|
selectedCellRef.current = cell;
|
|
949
1079
|
selectedCellAddressRef.current = getCellAddress(editor, cell);
|
|
@@ -963,14 +1093,31 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
963
1093
|
syncOverlay();
|
|
964
1094
|
}
|
|
965
1095
|
};
|
|
1096
|
+
// The pointer keeps an edge handle shown while it's on the handle.
|
|
1097
|
+
const updateEdgesFromTarget = (target: EventTarget | null) => {
|
|
1098
|
+
if (getTargetElement(target)?.closest("[data-table-edge-control]")) {
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
updateHoveredEdges(getHoveredTableEdges(editor, target));
|
|
1102
|
+
};
|
|
966
1103
|
const handleHoverPointerMove = (event: PointerEvent) => {
|
|
967
1104
|
if (pointerResizeRef.current) return;
|
|
968
|
-
|
|
1105
|
+
// Touch has no hover. A finger that moves during a tap would show a
|
|
1106
|
+
// handle under it before the tap lands, so taps use handleTapEdges.
|
|
1107
|
+
if (event.pointerType !== "touch") updateEdgesFromTarget(event.target);
|
|
1108
|
+
updateHoveredCell(event.target);
|
|
1109
|
+
};
|
|
1110
|
+
// Once a tap has placed the caret, the cell it landed on shows its edge
|
|
1111
|
+
// handles, so a second tap can use them.
|
|
1112
|
+
const handleTapEdges = (event: MouseEvent) => {
|
|
1113
|
+
if (pointerResizeRef.current) return;
|
|
1114
|
+
updateEdgesFromTarget(event.target);
|
|
969
1115
|
};
|
|
970
1116
|
|
|
971
1117
|
const handleKeyUp = () => {
|
|
972
1118
|
const cell = getCellFromEditorSelection(editor);
|
|
973
|
-
selectedCellRef.current =
|
|
1119
|
+
selectedCellRef.current =
|
|
1120
|
+
cell && isInEditor(view.dom, cell) ? cell : null;
|
|
974
1121
|
selectedCellAddressRef.current = selectedCellRef.current
|
|
975
1122
|
? getCellAddress(editor, selectedCellRef.current)
|
|
976
1123
|
: null;
|
|
@@ -984,7 +1131,7 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
984
1131
|
}
|
|
985
1132
|
|
|
986
1133
|
const cell = getCellFromEditorSelection(editor);
|
|
987
|
-
if (cell && view.dom
|
|
1134
|
+
if (cell && isInEditor(view.dom, cell)) {
|
|
988
1135
|
selectedCellRef.current = cell;
|
|
989
1136
|
selectedCellAddressRef.current = getCellAddress(editor, cell);
|
|
990
1137
|
} else if (!selectedCellRef.current?.isConnected) {
|
|
@@ -1016,6 +1163,7 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1016
1163
|
document.addEventListener("mousedown", handlePointerDown, true);
|
|
1017
1164
|
document.addEventListener("click", handlePointerDown, true);
|
|
1018
1165
|
document.addEventListener("pointermove", handleHoverPointerMove, true);
|
|
1166
|
+
document.addEventListener("click", handleTapEdges, true);
|
|
1019
1167
|
window.addEventListener("resize", handleGeometryChange);
|
|
1020
1168
|
window.addEventListener("scroll", handleScroll, true);
|
|
1021
1169
|
view.dom.addEventListener("keyup", handleKeyUp);
|
|
@@ -1026,10 +1174,12 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1026
1174
|
document.removeEventListener("mousedown", handlePointerDown, true);
|
|
1027
1175
|
document.removeEventListener("click", handlePointerDown, true);
|
|
1028
1176
|
document.removeEventListener("pointermove", handleHoverPointerMove, true);
|
|
1177
|
+
document.removeEventListener("click", handleTapEdges, true);
|
|
1029
1178
|
window.removeEventListener("resize", handleGeometryChange);
|
|
1030
1179
|
window.removeEventListener("scroll", handleScroll, true);
|
|
1031
1180
|
window.removeEventListener("pointermove", handlePointerMove);
|
|
1032
1181
|
window.removeEventListener("pointerup", handlePointerUp);
|
|
1182
|
+
window.removeEventListener("pointercancel", handlePointerCancel);
|
|
1033
1183
|
view.dom.removeEventListener("keyup", handleKeyUp);
|
|
1034
1184
|
editor.off("selectionUpdate", handleSelectionUpdate);
|
|
1035
1185
|
};
|
|
@@ -1118,6 +1268,29 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1118
1268
|
ROW_OPTIONS_MENU_HEIGHT >
|
|
1119
1269
|
viewportHeight - MENU_COLLISION_PADDING
|
|
1120
1270
|
: false;
|
|
1271
|
+
const hoveredAxisCell =
|
|
1272
|
+
!columnMenuOpen &&
|
|
1273
|
+
!rowMenuOpen &&
|
|
1274
|
+
hoveredCell?.isConnected &&
|
|
1275
|
+
hoveredCell !== selectedCell
|
|
1276
|
+
? hoveredCell
|
|
1277
|
+
: null;
|
|
1278
|
+
const hoveredAxisTable = hoveredAxisCell?.closest(
|
|
1279
|
+
"table",
|
|
1280
|
+
) as HTMLElement | null;
|
|
1281
|
+
const axisTableRect = hoveredAxisTable
|
|
1282
|
+
? hoveredAxisTable.getBoundingClientRect()
|
|
1283
|
+
: selectedTableRect;
|
|
1284
|
+
const axisScrollport = hoveredAxisTable
|
|
1285
|
+
? getTableScrollport(hoveredAxisTable)
|
|
1286
|
+
: selectedScrollport;
|
|
1287
|
+
const axisRect =
|
|
1288
|
+
hoveredAxisCell && hoveredAxisTable && axisScrollport
|
|
1289
|
+
? clampToScrollport(
|
|
1290
|
+
hoveredAxisCell.getBoundingClientRect(),
|
|
1291
|
+
axisScrollport,
|
|
1292
|
+
)
|
|
1293
|
+
: visibleSelectedRect;
|
|
1121
1294
|
const columnMenuAlign = columnMenuWouldOverflowRight ? "end" : "start";
|
|
1122
1295
|
const columnMenuAlignOffset = columnMenuWouldOverflowRight
|
|
1123
1296
|
? -COLUMN_OPTIONS_MENU_ALIGN_OFFSET
|
|
@@ -1131,8 +1304,9 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1131
1304
|
"content-table-axis-trigger group absolute z-40 flex items-center justify-center rounded-md bg-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2";
|
|
1132
1305
|
const axisMarkerClass =
|
|
1133
1306
|
"block rounded-full bg-muted-foreground/55 transition-colors group-hover:bg-primary group-focus-visible:bg-primary";
|
|
1307
|
+
// touch-none lets a finger drag a handle instead of scrolling the page.
|
|
1134
1308
|
const handleClass =
|
|
1135
|
-
"table-hover-controls group absolute z-40 flex items-center justify-center rounded-md bg-transparent opacity-0 transition-opacity hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2";
|
|
1309
|
+
"table-hover-controls group absolute z-40 flex touch-none items-center justify-center rounded-md bg-transparent opacity-0 transition-opacity hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2";
|
|
1136
1310
|
const edgeHandleMarkerClass =
|
|
1137
1311
|
"block rounded-full bg-muted-foreground/55 transition-colors group-hover:bg-primary group-focus-visible:bg-primary";
|
|
1138
1312
|
|
|
@@ -1181,15 +1355,24 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1181
1355
|
}}
|
|
1182
1356
|
/>
|
|
1183
1357
|
) : null}
|
|
1358
|
+
</>
|
|
1359
|
+
) : null}
|
|
1184
1360
|
|
|
1185
|
-
|
|
1361
|
+
{axisRect && axisScrollport && axisTableRect ? (
|
|
1362
|
+
<>
|
|
1363
|
+
<DropdownMenu
|
|
1364
|
+
open={columnMenuOpen}
|
|
1365
|
+
onOpenChange={(open) => {
|
|
1366
|
+
// Enter, Space and ArrowDown open the menu without a pointer.
|
|
1367
|
+
if (open) selectHoveredCell();
|
|
1368
|
+
setColumnMenuOpen(open);
|
|
1369
|
+
}}
|
|
1370
|
+
modal={false}
|
|
1371
|
+
>
|
|
1186
1372
|
<DropdownMenuTrigger asChild>
|
|
1187
1373
|
<button
|
|
1188
1374
|
aria-label={t("database.columnOptions")}
|
|
1189
|
-
className={cn(
|
|
1190
|
-
lineButtonClass,
|
|
1191
|
-
!visibleSelectedRect.width && "invisible",
|
|
1192
|
-
)}
|
|
1375
|
+
className={cn(lineButtonClass, !axisRect.width && "invisible")}
|
|
1193
1376
|
data-testid="table-column-options"
|
|
1194
1377
|
onClickCapture={(event) => {
|
|
1195
1378
|
event.preventDefault();
|
|
@@ -1207,12 +1390,9 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1207
1390
|
setColumnMenuOpen(true);
|
|
1208
1391
|
}}
|
|
1209
1392
|
style={{
|
|
1210
|
-
left:
|
|
1211
|
-
visibleSelectedRect.left -
|
|
1212
|
-
wrapperRect.left +
|
|
1213
|
-
visibleSelectedRect.width / 2,
|
|
1393
|
+
left: axisRect.left - wrapperRect.left + axisRect.width / 2,
|
|
1214
1394
|
top:
|
|
1215
|
-
|
|
1395
|
+
axisTableRect.top -
|
|
1216
1396
|
wrapperRect.top -
|
|
1217
1397
|
COLUMN_OPTIONS_HIT_HEIGHT / 2,
|
|
1218
1398
|
height: COLUMN_OPTIONS_HIT_HEIGHT,
|
|
@@ -1236,6 +1416,8 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1236
1416
|
align={columnMenuAlign}
|
|
1237
1417
|
alignOffset={columnMenuAlignOffset}
|
|
1238
1418
|
collisionPadding={MENU_COLLISION_PADDING}
|
|
1419
|
+
onCloseAutoFocus={returnFocusToEditor}
|
|
1420
|
+
onInteractOutside={noteMenuDismissedOutside}
|
|
1239
1421
|
>
|
|
1240
1422
|
<DropdownMenuLabel>{t("database.column")}</DropdownMenuLabel>
|
|
1241
1423
|
{canToggleHeaderColumn ? (
|
|
@@ -1278,7 +1460,7 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1278
1460
|
className="gap-2"
|
|
1279
1461
|
onSelect={() =>
|
|
1280
1462
|
runAtCell(getSelectedCellElement(), () => {
|
|
1281
|
-
editor
|
|
1463
|
+
userEditChain(editor).addColumnBefore().run();
|
|
1282
1464
|
})
|
|
1283
1465
|
}
|
|
1284
1466
|
>
|
|
@@ -1289,7 +1471,7 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1289
1471
|
className="gap-2"
|
|
1290
1472
|
onSelect={() =>
|
|
1291
1473
|
runAtCell(getSelectedCellElement(), () => {
|
|
1292
|
-
editor
|
|
1474
|
+
userEditChain(editor).addColumnAfter().run();
|
|
1293
1475
|
})
|
|
1294
1476
|
}
|
|
1295
1477
|
>
|
|
@@ -1316,9 +1498,9 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1316
1498
|
onSelect={() =>
|
|
1317
1499
|
runAtCell(getSelectedCellElement(), () => {
|
|
1318
1500
|
if (selectedDimensions.columnCount <= 1) {
|
|
1319
|
-
editor
|
|
1501
|
+
userEditChain(editor).deleteTable().run();
|
|
1320
1502
|
} else {
|
|
1321
|
-
editor
|
|
1503
|
+
userEditChain(editor).deleteColumn().run();
|
|
1322
1504
|
}
|
|
1323
1505
|
})
|
|
1324
1506
|
}
|
|
@@ -1329,7 +1511,15 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1329
1511
|
</DropdownMenuContent>
|
|
1330
1512
|
</DropdownMenu>
|
|
1331
1513
|
|
|
1332
|
-
<DropdownMenu
|
|
1514
|
+
<DropdownMenu
|
|
1515
|
+
open={rowMenuOpen}
|
|
1516
|
+
onOpenChange={(open) => {
|
|
1517
|
+
// Enter, Space and ArrowDown open the menu without a pointer.
|
|
1518
|
+
if (open) selectHoveredCell();
|
|
1519
|
+
setRowMenuOpen(open);
|
|
1520
|
+
}}
|
|
1521
|
+
modal={false}
|
|
1522
|
+
>
|
|
1333
1523
|
<DropdownMenuTrigger asChild>
|
|
1334
1524
|
<button
|
|
1335
1525
|
aria-label={t("database.rowOptions")}
|
|
@@ -1352,13 +1542,10 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1352
1542
|
}}
|
|
1353
1543
|
style={{
|
|
1354
1544
|
left:
|
|
1355
|
-
Math.max(
|
|
1545
|
+
Math.max(axisTableRect.left, axisScrollport.left) -
|
|
1356
1546
|
wrapperRect.left -
|
|
1357
1547
|
ROW_OPTIONS_HIT_WIDTH / 2,
|
|
1358
|
-
top:
|
|
1359
|
-
visibleSelectedRect.top -
|
|
1360
|
-
wrapperRect.top +
|
|
1361
|
-
visibleSelectedRect.height / 2,
|
|
1548
|
+
top: axisRect.top - wrapperRect.top + axisRect.height / 2,
|
|
1362
1549
|
height: ROW_OPTIONS_HIT_HEIGHT,
|
|
1363
1550
|
transform: "translateY(-50%)",
|
|
1364
1551
|
width: ROW_OPTIONS_HIT_WIDTH,
|
|
@@ -1377,6 +1564,8 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1377
1564
|
align={rowMenuAlign}
|
|
1378
1565
|
alignOffset={rowMenuAlignOffset}
|
|
1379
1566
|
collisionPadding={MENU_COLLISION_PADDING}
|
|
1567
|
+
onCloseAutoFocus={returnFocusToEditor}
|
|
1568
|
+
onInteractOutside={noteMenuDismissedOutside}
|
|
1380
1569
|
side="right"
|
|
1381
1570
|
>
|
|
1382
1571
|
<DropdownMenuLabel>{t("database.row")}</DropdownMenuLabel>
|
|
@@ -1420,7 +1609,7 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1420
1609
|
className="gap-2"
|
|
1421
1610
|
onSelect={() =>
|
|
1422
1611
|
runAtCell(getSelectedCellElement(), () => {
|
|
1423
|
-
editor
|
|
1612
|
+
userEditChain(editor).addRowBefore().run();
|
|
1424
1613
|
})
|
|
1425
1614
|
}
|
|
1426
1615
|
>
|
|
@@ -1431,7 +1620,7 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1431
1620
|
className="gap-2"
|
|
1432
1621
|
onSelect={() =>
|
|
1433
1622
|
runAtCell(getSelectedCellElement(), () => {
|
|
1434
|
-
editor
|
|
1623
|
+
userEditChain(editor).addRowAfter().run();
|
|
1435
1624
|
})
|
|
1436
1625
|
}
|
|
1437
1626
|
>
|
|
@@ -1458,9 +1647,9 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1458
1647
|
onSelect={() =>
|
|
1459
1648
|
runAtCell(getSelectedCellElement(), () => {
|
|
1460
1649
|
if (selectedDimensions.rowCount <= 1) {
|
|
1461
|
-
editor
|
|
1650
|
+
userEditChain(editor).deleteTable().run();
|
|
1462
1651
|
} else {
|
|
1463
|
-
editor
|
|
1652
|
+
userEditChain(editor).deleteRow().run();
|
|
1464
1653
|
}
|
|
1465
1654
|
})
|
|
1466
1655
|
}
|
|
@@ -1481,59 +1670,59 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1481
1670
|
const scrollportRect = getTableScrollport(table);
|
|
1482
1671
|
const visibleLeft = Math.max(rect.left, scrollportRect.left);
|
|
1483
1672
|
const visibleRight = Math.min(rect.right, scrollportRect.right);
|
|
1484
|
-
const isTableEndVisible = rect.right <= visibleRight + 1;
|
|
1485
1673
|
const top = rect.top - wrapperRect.top;
|
|
1486
|
-
const right =
|
|
1674
|
+
const right = visibleRight - wrapperRect.left;
|
|
1487
1675
|
const bottom = rect.bottom - wrapperRect.top;
|
|
1488
1676
|
const visibleCenter =
|
|
1489
1677
|
(visibleLeft + visibleRight) / 2 - wrapperRect.left;
|
|
1678
|
+
// A hidden handle lets clicks through to the cell under it.
|
|
1490
1679
|
const isColumnHandleVisible = hoveredEdges.columnTable === table;
|
|
1491
1680
|
const isRowHandleVisible = hoveredEdges.rowTable === table;
|
|
1492
1681
|
|
|
1493
1682
|
return (
|
|
1494
1683
|
<div key={tableId}>
|
|
1495
|
-
{
|
|
1496
|
-
<
|
|
1497
|
-
<
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1684
|
+
<Tooltip delayDuration={EDGE_HANDLE_TOOLTIP_DELAY}>
|
|
1685
|
+
<TooltipTrigger asChild>
|
|
1686
|
+
<button
|
|
1687
|
+
aria-label={t("database.tableColumnHandle")}
|
|
1688
|
+
className={cn(
|
|
1689
|
+
handleClass,
|
|
1690
|
+
"cursor-col-resize -translate-y-1/2",
|
|
1691
|
+
isColumnHandleVisible
|
|
1692
|
+
? "opacity-100"
|
|
1693
|
+
: "pointer-events-none",
|
|
1694
|
+
)}
|
|
1695
|
+
data-table-edge-control
|
|
1696
|
+
data-table-edge="column"
|
|
1697
|
+
data-table-id={tableId}
|
|
1698
|
+
data-testid="table-column-handle"
|
|
1699
|
+
onPointerDown={(event) =>
|
|
1700
|
+
startHandlePointer("column", table, event)
|
|
1701
|
+
}
|
|
1702
|
+
style={{
|
|
1703
|
+
height: COLUMN_EDGE_HANDLE_HIT_HEIGHT,
|
|
1704
|
+
left: right - COLUMN_EDGE_HANDLE_HIT_WIDTH / 2,
|
|
1705
|
+
top: top + rect.height / 2,
|
|
1706
|
+
width: COLUMN_EDGE_HANDLE_HIT_WIDTH,
|
|
1707
|
+
}}
|
|
1708
|
+
type="button"
|
|
1709
|
+
>
|
|
1710
|
+
<span
|
|
1711
|
+
className={edgeHandleMarkerClass}
|
|
1512
1712
|
style={{
|
|
1513
|
-
height:
|
|
1514
|
-
|
|
1515
|
-
top: top + rect.height / 2,
|
|
1516
|
-
width: COLUMN_EDGE_HANDLE_HIT_WIDTH,
|
|
1713
|
+
height: EDGE_HANDLE_LENGTH,
|
|
1714
|
+
width: EDGE_HANDLE_THICKNESS,
|
|
1517
1715
|
}}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
</TooltipTrigger>
|
|
1529
|
-
<TooltipContent>
|
|
1530
|
-
<strong>{t("database.click")}</strong>{" "}
|
|
1531
|
-
{t("database.toAddAColumn")}{" "}
|
|
1532
|
-
<strong>{t("database.drag")}</strong>{" "}
|
|
1533
|
-
{t("database.toAddOrRemoveColumns")}
|
|
1534
|
-
</TooltipContent>
|
|
1535
|
-
</Tooltip>
|
|
1536
|
-
) : null}
|
|
1716
|
+
/>
|
|
1717
|
+
</button>
|
|
1718
|
+
</TooltipTrigger>
|
|
1719
|
+
<TooltipContent>
|
|
1720
|
+
<strong>{t("database.click")}</strong>{" "}
|
|
1721
|
+
{t("database.toAddAColumn")}{" "}
|
|
1722
|
+
<strong>{t("database.drag")}</strong>{" "}
|
|
1723
|
+
{t("database.toAddOrRemoveColumns")}
|
|
1724
|
+
</TooltipContent>
|
|
1725
|
+
</Tooltip>
|
|
1537
1726
|
|
|
1538
1727
|
<Tooltip delayDuration={EDGE_HANDLE_TOOLTIP_DELAY}>
|
|
1539
1728
|
<TooltipTrigger asChild>
|
|
@@ -1542,7 +1731,7 @@ export function TableHoverControls({ editor }: TableHoverControlsProps) {
|
|
|
1542
1731
|
className={cn(
|
|
1543
1732
|
handleClass,
|
|
1544
1733
|
"cursor-row-resize -translate-x-1/2",
|
|
1545
|
-
isRowHandleVisible
|
|
1734
|
+
isRowHandleVisible ? "opacity-100" : "pointer-events-none",
|
|
1546
1735
|
)}
|
|
1547
1736
|
data-table-edge-control
|
|
1548
1737
|
data-table-edge="row"
|