alex/auto-undo-redo: clean up page rename undo/redo

pull/3364/head
alex 2024-04-24 15:33:59 +01:00
rodzic 0d11d3d253
commit e341dbea06
8 zmienionych plików z 165 dodań i 29 usunięć

Wyświetl plik

@ -401,6 +401,7 @@ describe('history options', () => {
manager.batch(
() => {
setA(1)
// even though we set this to record, it will still be ignored
manager.batch(() => setB(1), { history: 'record' })
setA(2)
},
@ -408,9 +409,9 @@ describe('history options', () => {
)
expect(getState()).toMatchObject({ a: 2, b: 1 })
// changes to A were ignore, but changes to B were recorded:
// changes were ignored:
manager.undo()
expect(getState()).toMatchObject({ a: 2, b: 0 })
expect(getState()).toMatchObject({ a: 2, b: 1 })
manager.mark()
manager.batch(
@ -429,8 +430,5 @@ describe('history options', () => {
// We can still redo because we preserved the redo stack:
manager.redo()
expect(getState()).toMatchObject({ a: 3, b: 2 })
manager.redo()
expect(getState()).toMatchObject({ a: 3, b: 1 })
})
})

Wyświetl plik

@ -86,7 +86,11 @@ export class HistoryManager<R extends UnknownRecord> {
_isInBatch = false
batch = (fn: () => void, opts?: TLHistoryBatchOptions) => {
const previousState = this.state
this.state = opts?.history ? modeToState[opts.history] : this.state
// we move to the new state only if we haven't explicitly paused
if (previousState !== HistoryRecorderState.Paused && opts?.history) {
this.state = modeToState[opts.history]
}
try {
if (this._isInBatch) {

Wyświetl plik

@ -61,6 +61,9 @@ export function createRecordMigrationSequence(opts: {
// @public
export function createRecordType<R extends UnknownRecord>(typeName: R['typeName'], config: {
ephemeralKeys?: {
readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean;
};
scope: RecordScope;
validator?: StoreValidator<R>;
}): RecordType<R, keyof Omit<R, 'id' | 'typeName'>>;
@ -193,6 +196,9 @@ export class RecordType<R extends UnknownRecord, RequiredProperties extends keyo
constructor(
typeName: R['typeName'], config: {
readonly createDefaultProperties: () => Exclude<OmitMeta<R>, RequiredProperties>;
readonly ephemeralKeys?: {
readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean;
};
readonly scope?: RecordScope;
readonly validator?: StoreValidator<R>;
});
@ -203,6 +209,12 @@ export class RecordType<R extends UnknownRecord, RequiredProperties extends keyo
// (undocumented)
readonly createDefaultProperties: () => Exclude<OmitMeta<R>, RequiredProperties>;
createId(customUniquePart?: string): IdOf<R>;
// (undocumented)
readonly ephemeralKeys?: {
readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean;
};
// (undocumented)
readonly ephemeralKeySet: ReadonlySet<string>;
isId(id?: string): id is IdOf<R>;
isInstance: (record?: UnknownRecord) => record is R;
parseId(id: IdOf<R>): string;
@ -265,7 +277,10 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
addHistoryInterceptor(fn: (entry: HistoryEntry<R>, source: ChangeSource) => void): () => void;
allRecords: () => R[];
// (undocumented)
applyDiff(diff: RecordsDiff<R>, runCallbacks?: boolean): void;
applyDiff(diff: RecordsDiff<R>, { runCallbacks, ignoreEphemeralKeys, }?: {
ignoreEphemeralKeys?: boolean;
runCallbacks?: boolean;
}): void;
// @internal (undocumented)
atomic<T>(fn: () => T, runCallbacks?: boolean): T;
clear: () => void;
@ -342,6 +357,8 @@ export class StoreSchema<R extends UnknownRecord, P = unknown> {
createIntegrityChecker(store: Store<R, P>): (() => void) | undefined;
// (undocumented)
getMigrationsSince(persistedSchema: SerializedSchema): Result<Migration[], string>;
// @internal (undocumented)
getType(typeName: string): RecordType<R, any>;
// (undocumented)
migratePersistedRecord(record: R, persistedSchema: SerializedSchema, direction?: 'down' | 'up'): MigrationResult<R>;
// (undocumented)

Wyświetl plik

@ -802,7 +802,16 @@
},
{
"kind": "Content",
"text": "{\n scope: "
"text": "{\n ephemeralKeys?: {\n readonly [K in "
},
{
"kind": "Reference",
"text": "Exclude",
"canonicalReference": "!Exclude:type"
},
{
"kind": "Content",
"text": "<keyof R, 'id' | 'typeName'>]: boolean;\n };\n scope: "
},
{
"kind": "Reference",
@ -851,8 +860,8 @@
],
"fileUrlPath": "packages/store/src/lib/RecordType.ts",
"returnTypeTokenRange": {
"startIndex": 11,
"endIndex": 15
"startIndex": 13,
"endIndex": 17
},
"releaseTag": "Public",
"overloadIndex": 1,
@ -869,7 +878,7 @@
"parameterName": "config",
"parameterTypeTokenRange": {
"startIndex": 5,
"endIndex": 10
"endIndex": 12
},
"isOptional": false
}
@ -2053,7 +2062,16 @@
},
{
"kind": "Content",
"text": "<R>, RequiredProperties>;\n readonly scope?: "
"text": "<R>, RequiredProperties>;\n readonly ephemeralKeys?: {\n readonly [K in "
},
{
"kind": "Reference",
"text": "Exclude",
"canonicalReference": "!Exclude:type"
},
{
"kind": "Content",
"text": "<keyof R, 'id' | 'typeName'>]: boolean;\n };\n readonly scope?: "
},
{
"kind": "Reference",
@ -2094,7 +2112,7 @@
"parameterName": "config",
"parameterTypeTokenRange": {
"startIndex": 3,
"endIndex": 12
"endIndex": 14
},
"isOptional": false
}
@ -2373,6 +2391,80 @@
"isAbstract": false,
"name": "createId"
},
{
"kind": "Property",
"canonicalReference": "@tldraw/store!RecordType#ephemeralKeys:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "readonly ephemeralKeys?: "
},
{
"kind": "Content",
"text": "{\n readonly [K in "
},
{
"kind": "Reference",
"text": "Exclude",
"canonicalReference": "!Exclude:type"
},
{
"kind": "Content",
"text": "<keyof R, 'id' | 'typeName'>]: boolean;\n }"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": true,
"isOptional": true,
"releaseTag": "Public",
"name": "ephemeralKeys",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 4
},
"isStatic": false,
"isProtected": false,
"isAbstract": false
},
{
"kind": "Property",
"canonicalReference": "@tldraw/store!RecordType#ephemeralKeySet:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "readonly ephemeralKeySet: "
},
{
"kind": "Reference",
"text": "ReadonlySet",
"canonicalReference": "!ReadonlySet:interface"
},
{
"kind": "Content",
"text": "<string>"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": true,
"isOptional": false,
"releaseTag": "Public",
"name": "ephemeralKeySet",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 3
},
"isStatic": false,
"isProtected": false,
"isAbstract": false
},
{
"kind": "Method",
"canonicalReference": "@tldraw/store!RecordType#isId:member(1)",
@ -3408,11 +3500,11 @@
},
{
"kind": "Content",
"text": ", runCallbacks?: "
"text": ", { runCallbacks, ignoreEphemeralKeys, }?: "
},
{
"kind": "Content",
"text": "boolean"
"text": "{\n ignoreEphemeralKeys?: boolean;\n runCallbacks?: boolean;\n }"
},
{
"kind": "Content",
@ -3445,7 +3537,7 @@
"isOptional": false
},
{
"parameterName": "runCallbacks",
"parameterName": "{ runCallbacks, ignoreEphemeralKeys, }",
"parameterTypeTokenRange": {
"startIndex": 4,
"endIndex": 5

Wyświetl plik

@ -2165,6 +2165,8 @@ export interface TLUiInputProps {
// (undocumented)
onComplete?: (value: string) => void;
// (undocumented)
onFocus?: () => void;
// (undocumented)
onValueChange?: (value: string) => void;
// (undocumented)
placeholder?: string;

Wyświetl plik

@ -24472,6 +24472,33 @@
"endIndex": 2
}
},
{
"kind": "PropertySignature",
"canonicalReference": "tldraw!TLUiInputProps#onFocus:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "onFocus?: "
},
{
"kind": "Content",
"text": "() => void"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": false,
"isOptional": true,
"releaseTag": "Public",
"name": "onFocus",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
},
{
"kind": "PropertySignature",
"canonicalReference": "tldraw!TLUiInputProps#onValueChange:member",

Wyświetl plik

@ -15,18 +15,12 @@ export const PageItemInput = function PageItemInput({
const rInput = useRef<HTMLInputElement | null>(null)
const handleFocus = useCallback(() => {
editor.mark('rename page')
}, [editor])
const handleChange = useCallback(
(value: string) => {
editor.history.ignore(() => {
editor.renamePage(id, value ? value : 'New Page')
})
},
[editor, id]
)
const handleComplete = useCallback(
(value: string) => {
editor.mark('rename page')
editor.renamePage(id, value || 'New Page')
},
[editor, id]
@ -38,8 +32,7 @@ export const PageItemInput = function PageItemInput({
ref={(el) => (rInput.current = el)}
defaultValue={name}
onValueChange={handleChange}
onComplete={handleComplete}
onCancel={handleComplete}
onFocus={handleFocus}
shouldManuallyMaintainScrollPositionWhenFocused
autofocus={isCurrentPage}
autoselect

Wyświetl plik

@ -21,6 +21,7 @@ export interface TLUiInputProps {
onValueChange?: (value: string) => void
onCancel?: (value: string) => void
onBlur?: (value: string) => void
onFocus?: () => void
className?: string
/**
* Usually on iOS when you focus an input, the browser will adjust the viewport to bring the input
@ -49,6 +50,7 @@ export const TldrawUiInput = React.forwardRef<HTMLInputElement, TLUiInputProps>(
onComplete,
onValueChange,
onCancel,
onFocus,
onBlur,
shouldManuallyMaintainScrollPositionWhenFocused = false,
children,
@ -77,8 +79,9 @@ export const TldrawUiInput = React.forwardRef<HTMLInputElement, TLUiInputProps>(
elm.select()
}
})
onFocus?.()
},
[autoselect]
[autoselect, onFocus]
)
const handleChange = React.useCallback(