snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import { Editor, getIndexBetween, getIndicesBetween, TLParentId } from 'tldraw'
|
||||
|
||||
/**
|
||||
* This function registers side effects that will make sure connection shapes are always below all
|
||||
* other shapes in the same parent. We do this because we don't want to connections on top of nodes,
|
||||
* which might be distracting.
|
||||
*/
|
||||
export function keepConnectionsAtBottom(editor: Editor) {
|
||||
// whenever an operation happens, we'll keep track of the parent ids that we might need to
|
||||
// re-sort the children of. An operation is any atomic change to the tldraw store.
|
||||
let pendingChangedParentIds = new Set<TLParentId>()
|
||||
|
||||
// whenever a shape is created, we'll add the parent id to the set of parent ids that we might
|
||||
// need to re-sort the children of.
|
||||
editor.sideEffects.registerAfterCreateHandler('shape', (shape, source) => {
|
||||
if (source === 'remote') return
|
||||
pendingChangedParentIds.add(shape.parentId)
|
||||
})
|
||||
// whenever a shape's index or parent id changes, we'll add the parent id to the set of parent
|
||||
// ids that we might need to re-sort the children of.
|
||||
editor.sideEffects.registerAfterChangeHandler('shape', (oldShape, newShape, source) => {
|
||||
if (source === 'remote') return
|
||||
if (oldShape.parentId === newShape.parentId && oldShape.index === newShape.index) return
|
||||
pendingChangedParentIds.add(newShape.parentId)
|
||||
})
|
||||
|
||||
// then, when the operation is complete, we re-sort the children as needed:
|
||||
editor.sideEffects.registerOperationCompleteHandler(() => {
|
||||
if (pendingChangedParentIds.size === 0) return
|
||||
|
||||
// reset the set of pending changed parent ids
|
||||
const changedParentIds = pendingChangedParentIds
|
||||
pendingChangedParentIds = new Set()
|
||||
|
||||
const updates = []
|
||||
|
||||
for (const parentId of changedParentIds) {
|
||||
// iterate through all the children of this parent. We want all the connections at the
|
||||
// bottom (first), and everything else after that. Because this fn runs all the time,
|
||||
// things should always be roughly in the right place to begin with. Because of that,
|
||||
// we'll just find any non-connections that are in the wrong place and move them up.
|
||||
|
||||
const childIds = editor.getSortedChildIdsForParent(parentId)
|
||||
|
||||
// we iterate in reverse because we want to find the highest connection index:
|
||||
let i = childIds.length - 1
|
||||
let highestConnectionIndex = null
|
||||
let nextIndexAboveHighestConnectionIndex = null
|
||||
for (; i >= 0; i--) {
|
||||
const child = editor.getShape(childIds[i])
|
||||
if (!child) continue
|
||||
|
||||
if (child.type === 'connection') {
|
||||
highestConnectionIndex = child.index
|
||||
break
|
||||
} else {
|
||||
nextIndexAboveHighestConnectionIndex = child.index
|
||||
}
|
||||
}
|
||||
|
||||
// now that we've found the highest connection index, we switch to looking for
|
||||
// non-connection shapes. these need to be moved up above the highest connection index.
|
||||
const shapesToMove = []
|
||||
for (; i >= 0; i--) {
|
||||
const child = editor.getShape(childIds[i])
|
||||
if (child && child.type !== 'connection') shapesToMove.push(child)
|
||||
}
|
||||
|
||||
// the shapesToMove array will be in reverse order, so we need to reverse it:
|
||||
shapesToMove.reverse()
|
||||
|
||||
// now we need the new indexes for each shape. These need to stay in the same relative
|
||||
// order, and be inserted above the `hightestConnectionIndex`, but below the index of
|
||||
// the next non-connection shape.
|
||||
const newIndexes = getIndicesBetween(
|
||||
highestConnectionIndex,
|
||||
nextIndexAboveHighestConnectionIndex,
|
||||
shapesToMove.length
|
||||
)
|
||||
|
||||
// now we can create the update partials for those shapes:
|
||||
for (let i = 0; i < shapesToMove.length; i++) {
|
||||
const shape = shapesToMove[i]
|
||||
updates.push({ id: shape.id, type: shape.type, index: newIndexes[i] } as const)
|
||||
}
|
||||
}
|
||||
|
||||
editor.updateShapes(updates)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of the next connection shape in the given parent. This will be above all other
|
||||
* connections, but the connections should be below all other shapes.
|
||||
*/
|
||||
export function getNextConnectionIndex(
|
||||
editor: Editor,
|
||||
parentId: TLParentId = editor.getCurrentPageId()
|
||||
) {
|
||||
const childIds = editor.getSortedChildIdsForParent(parentId)
|
||||
|
||||
let prevIndex = null
|
||||
let highestConnectionIndex = null
|
||||
for (let i = childIds.length - 1; i >= 0; i--) {
|
||||
const child = editor.getShape(childIds[i])
|
||||
if (!child) continue
|
||||
|
||||
if (child.type === 'connection') {
|
||||
highestConnectionIndex = child.index
|
||||
break
|
||||
}
|
||||
prevIndex = child.index
|
||||
}
|
||||
|
||||
return getIndexBetween(highestConnectionIndex, prevIndex)
|
||||
}
|
||||
Reference in New Issue
Block a user