Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
84559a5
chore(json-crdt-peritext-ui): 🤖 update deps
streamich Jan 18, 2025
5f98238
feat(json-crdt-peritext-ui): 🎸 track state of caret visibility
streamich Jan 18, 2025
26ccfab
feat(json-crdt-peritext-ui): 🎸 improve caret toolbar event tracking
streamich Jan 18, 2025
79b0b72
refactor(json-crdt-peritext-ui): 💡 improve menu construction setup
streamich Jan 20, 2025
478ed93
feat(json-crdt-peritext-ui): 🎸 setup selection above-focus rendering
streamich Jan 20, 2025
cf05b3a
feat(json-crdt-peritext-ui): 🎸 make mouse down state reactive
streamich Jan 21, 2025
89b4956
feat(json-crdt-peritext-ui): 🎸 start focus floating menu implementation
streamich Jan 21, 2025
8b4afde
feat(json-crdt-peritext-ui): 🎸 improve on floating menus
streamich Jan 22, 2025
c221a77
feat(json-crdt-peritext-ui): 🎸 do not emite change event if old value…
streamich Jan 23, 2025
8b1c7b1
feat(json-crdt-peritext-ui): 🎸 do not show focus floating menu while …
streamich Jan 24, 2025
2fc72c5
feat(json-crdt-peritext-ui): 🎸 delay showing floating menus
streamich Jan 25, 2025
549253e
feat(json-crdt-peritext-ui): 🎸 improve toolbar state management
streamich Jan 25, 2025
621e4d2
feat(json-crdt-peritext-ui): 🎸 hide floating menu when focus blurred
streamich Jan 25, 2025
736d8e8
feat(json-crdt-peritext-ui): 🎸 do not change cursor position when edi…
streamich Jan 25, 2025
dfbfa9c
feat(json-crdt-peritext-ui): 🎸 modularize the timeout component
streamich Jan 25, 2025
401a3c7
chore(json-crdt-peritext-ui): 🤖 update UI dependency
streamich Jan 25, 2025
9998a33
feat(json-crdt-peritext-ui): 🎸 add cooldown for disable status when f…
streamich Jan 25, 2025
ae4926d
feat(json-crdt-peritext-ui): 🎸 introduce Peritext rendering surface A…
streamich Jan 27, 2025
d7b4fae
feat(json-crdt-peritext-ui): 🎸 re-focus on item click
streamich Jan 27, 2025
fca722a
feat(json-crdt-peritext-ui): 🎸 add bolding action
streamich Jan 27, 2025
8649c71
feat(json-crdt-peritext-ui): 🎸 update nice-ui dependency
streamich Jan 30, 2025
da73151
feat(json-crdt-peritext-ui): 🎸 add dependency list to timeout hook
streamich Jan 30, 2025
02fac8d
chore(json-crdt-peritext-ui): 🤖 update toolbar display
streamich Jan 30, 2025
9d32464
chore(json-crdt-peritext-ui): 🤖 disable state change on mouse down
streamich Jan 31, 2025
6c70a7e
feat(json-crdt-peritext-ui): 🎸 advance on facus selection floating to…
streamich Feb 1, 2025
50a5cfb
feat(json-crdt-peritext-ui): 🎸 do not show focus floating menu when s…
streamich Feb 5, 2025
820a3f1
chore(json-crdt-peritext-ui): 🤖 do some tweaks to menu
streamich Feb 13, 2025
5e96bbf
Merge branch 'master' into inline-floating-menu
streamich Feb 14, 2025
7b558d2
style: 💄 run formatter
streamich Feb 14, 2025
bf2fbcc
style: 💄 fix linter issues
streamich Feb 14, 2025
3a0e5b6
chore: 🤖 move keywords down in file
streamich Feb 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 45 additions & 45 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,48 +16,6 @@
"engines": {
"node": ">=10.0"
},
"keywords": [
"collaborative",
"multiplayer",
"local-first",
"localfirst",
"crdt",
"rdt",
"ot",
"operational-transformation",
"replicated",
"sync",
"synchronization",
"distributed-state",
"marshaling",
"serializations",
"json-patch",
"json-binary",
"json-brand",
"json-cli",
"json-clone",
"json-crdt-patch",
"json-crdt-extensions",
"json-crdt-peritext-ui",
"json-crdt",
"json-equal",
"json-expression",
"json-hash",
"json-ot",
"json-pack",
"json-patch-multicore",
"json-patch-ot",
"json-patch",
"json-pointer",
"json-random",
"json-schema",
"json-size",
"json-stable",
"json-text",
"json-type",
"json-type-value",
"json-walk"
],
"main": "lib/index.js",
"types": "lib/index.d.ts",
"typings": "lib/index.d.ts",
Expand All @@ -81,7 +39,7 @@
"format": "biome format ./src",
"format:fix": "biome format --write ./src",
"lint": "biome lint ./src",
"lint:fix": "biome lint --apply ./src",
"lint:fix": "biome lint --write ./src",
"clean": "npx rimraf@5.0.5 lib es6 es2019 es2020 esm typedocs coverage gh-pages yarn-error.log src/**/__bench__/node_modules src/**/__bench__/yarn-error.log",
"build:es2020": "tsc --project tsconfig.build.json --module commonjs --target es2020 --outDir lib",
"build:esm": "tsc --project tsconfig.build.json --module ESNext --target ESNEXT --outDir esm",
Expand Down Expand Up @@ -156,7 +114,7 @@
"json-crdt-traces": "https://github.com/streamich/json-crdt-traces#ec825401dc05cbb74b9e0b3c4d6527399f54d54d",
"json-logic-js": "^2.0.2",
"nano-theme": "^1.4.3",
"nice-ui": "^1.25.0",
"nice-ui": "^1.28.0",
"quill-delta": "^5.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand Down Expand Up @@ -228,5 +186,47 @@
"@semantic-release/npm",
"@semantic-release/git"
]
}
},
"keywords": [
"collaborative",
"multiplayer",
"local-first",
"localfirst",
"crdt",
"rdt",
"ot",
"operational-transformation",
"replicated",
"sync",
"synchronization",
"distributed-state",
"marshaling",
"serializations",
"json-patch",
"json-binary",
"json-brand",
"json-cli",
"json-clone",
"json-crdt-patch",
"json-crdt-extensions",
"json-crdt-peritext-ui",
"json-crdt",
"json-equal",
"json-expression",
"json-hash",
"json-ot",
"json-pack",
"json-patch-multicore",
"json-patch-ot",
"json-patch",
"json-pointer",
"json-random",
"json-schema",
"json-size",
"json-stable",
"json-text",
"json-type",
"json-type-value",
"json-walk"
]
}
2 changes: 2 additions & 0 deletions src/json-crdt-extensions/peritext/slice/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const enum SliceTypeCon {
iaside = -25, // Inline <aside>
iembed = -26, // inline embed (any media, dropdown, Google Docs-like chips: date, person, file, etc.)
bookmark = -27, // UI for creating a link to this slice
overline = -28, // <span style="text-decoration: overline">
}

/**
Expand Down Expand Up @@ -131,6 +132,7 @@ export enum SliceTypeName {
iaside = SliceTypeCon.iaside,
iembed = SliceTypeCon.iembed,
bookmark = SliceTypeCon.bookmark,
overline = SliceTypeCon.overline,
}

export enum SliceHeaderMask {
Expand Down
21 changes: 11 additions & 10 deletions src/json-crdt-peritext-ui/dom/CursorController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export class CursorController implements UiLifeCycles, Printable {

public readonly focus = new ValueSyncStore<boolean>(false);

private readonly onFocus = (): void => {
private readonly onFocus = (event: Event): void => {
this.focus.next(true);
};

Expand All @@ -129,15 +129,16 @@ export class CursorController implements UiLifeCycles, Printable {

private x = 0;
private y = 0;
private mouseDown: boolean = false;
public readonly mouseDown = new ValueSyncStore<boolean>(false);

private readonly onMouseDown = (ev: MouseEvent): void => {
if (!this.focus.value && this.opts.txt.editor.hasCursor()) return;
const {clientX, clientY} = ev;
this.x = clientX;
this.y = clientY;
switch (ev.detail) {
case 1: {
this.mouseDown = false;
this.mouseDown.next(false);
const at = this.posAtPoint(clientX, clientY);
if (at === -1) return;
this.selAnchor = at;
Expand All @@ -150,32 +151,32 @@ export class CursorController implements UiLifeCycles, Printable {
ev.preventDefault();
et.cursor({at, edge: 'new'});
} else {
this.mouseDown = true;
this.mouseDown.next(true);
ev.preventDefault();
et.cursor({at});
}
break;
}
case 2:
this.mouseDown = false;
this.mouseDown.next(false);
ev.preventDefault();
this.opts.et.cursor({unit: 'word'});
break;
case 3:
this.mouseDown = false;
this.mouseDown.next(false);
ev.preventDefault();
this.opts.et.cursor({unit: 'block'});
break;
case 4:
this.mouseDown = false;
this.mouseDown.next(false);
ev.preventDefault();
this.opts.et.cursor({unit: 'all'});
break;
}
};

private readonly onMouseMove = (ev: MouseEvent): void => {
if (!this.mouseDown) return;
if (!this.mouseDown.value) return;
const at = this.selAnchor;
if (at < 0) return;
const {clientX, clientY} = ev;
Expand All @@ -190,7 +191,7 @@ export class CursorController implements UiLifeCycles, Printable {
};

private readonly onMouseUp = (ev: MouseEvent): void => {
this.mouseDown = false;
this.mouseDown.next(false);
};

private onKeyDown = (event: KeyboardEvent): void => {
Expand Down Expand Up @@ -242,6 +243,6 @@ export class CursorController implements UiLifeCycles, Printable {
/** ----------------------------------------------------- {@link Printable} */

public toString(tab?: string): string {
return `cursor { focus: ${this.focus.value}, x: ${this.x}, y: ${this.y}, mouseDown: ${this.mouseDown} }`;
return `cursor { focus: ${this.focus.value}, x: ${this.x}, y: ${this.y}, mouseDown: ${this.mouseDown.value} }`;
}
}
10 changes: 8 additions & 2 deletions src/json-crdt-peritext-ui/dom/DomController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import {KeyController} from '../dom/KeyController';
import {CompositionController} from '../dom/CompositionController';
import type {PeritextEventDefaults} from '../events/PeritextEventDefaults';
import type {PeritextEventTarget} from '../events/PeritextEventTarget';
import type {UiLifeCycles} from '../dom/types';
import type {PeritextRenderingSurfaceApi, UiLifeCycles} from '../dom/types';

export interface DomControllerOpts {
source: HTMLElement;
events: PeritextEventDefaults;
}

export class DomController implements UiLifeCycles, Printable {
export class DomController implements UiLifeCycles, Printable, PeritextRenderingSurfaceApi {
public readonly et: PeritextEventTarget;
public readonly keys: KeyController;
public readonly comp: CompositionController;
Expand Down Expand Up @@ -50,6 +50,12 @@ export class DomController implements UiLifeCycles, Printable {
this.richText.stop();
}

/** ----------------------------------- {@link PeritextRenderingSurfaceApi} */

public focus(): void {
this.opts.source.focus();
}

/** ----------------------------------------------------- {@link Printable} */

public toString(tab?: string): string {
Expand Down
7 changes: 7 additions & 0 deletions src/json-crdt-peritext-ui/dom/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,10 @@ export interface UiLifeCyclesRender {
}

export type Rect = Pick<DOMRect, 'x' | 'y' | 'width' | 'height'>;

export interface PeritextRenderingSurfaceApi {
/**
* Focuses the rendering surface, so that it can receive keyboard input.
*/
focus(): void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ export interface TopToolbarProps {
}

export const TopToolbar: React.FC<TopToolbarProps> = ({ctx}) => {
const pending = useSyncStore(ctx.peritext.editor.pending);
const peritext = ctx.peritext;
const editor = peritext.editor;
const pending = useSyncStore(editor.pending);

if (!ctx.dom) return null;

const [complete] = ctx.peritext.overlay.stat(ctx.peritext.editor.cursor);
const [complete] = editor.hasCursor() ? peritext.overlay.stat(editor.cursor) : [new Set()];

const inlineGroupButton = (type: string | number, name: React.ReactNode) => (
<Button
Expand Down
45 changes: 30 additions & 15 deletions src/json-crdt-peritext-ui/plugins/toolbar/RenderCaret.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
// biome-ignore lint: React is used for JSX
import * as React from 'react';
import {rule} from 'nano-theme';
import {CaretToolbar} from './CaretToolbar';
import type {CaretViewProps} from '../../react/cursor/CaretView';
import {CaretToolbar} from 'nice-ui/lib/4-card/Toolbar/ToolbarMenu/CaretToolbar';
import {useToolbarPlugin} from './context';
import type {PeritextEventDetailMap} from '../../events/types';
import {useSyncStore, useSyncStoreOpt, useTimeout} from '../../react/hooks';
import {AfterTimeout} from '../../react/util/AfterTimeout';
import type {CaretViewProps} from '../../react/cursor/CaretView';

const height = 1.9;
const height = 1.8;

const blockClass = rule({
pos: 'relative',
w: '0px',
h: '100%',
bg: 'black',
va: 'bottom',
});

Expand All @@ -23,9 +22,6 @@ const overClass = rule({
isolation: 'isolate',
us: 'none',
transform: 'translateX(calc(-50% + 0px))',
// w: '1px',
// h: '1px',
// bd: '1px solid red',
});

export interface RenderCaretProps extends CaretViewProps {
Expand All @@ -34,17 +30,36 @@ export interface RenderCaretProps extends CaretViewProps {

export const RenderCaret: React.FC<RenderCaretProps> = ({children}) => {
const {toolbar} = useToolbarPlugin()!;
const showInlineToolbar = toolbar.showInlineToolbar;
const [showCaretToolbarValue, toolbarVisibilityChangeTime] = useSyncStore(showInlineToolbar);
const focus = useSyncStoreOpt(toolbar.surface.dom?.cursor.focus) || false;
const doHideForCoolDown = toolbarVisibilityChangeTime + 500 > Date.now();
const enableAfterCoolDown = useTimeout(500, [doHideForCoolDown]);

// biome-ignore lint/correctness/useExhaustiveDependencies: showInlineToolbar.next do not need to memoize
const handleClose = React.useCallback(() => {
setTimeout(() => {
if (showInlineToolbar.value) showInlineToolbar.next([false, Date.now()]);
}, 5);
}, []);

let toolbarElement = (
<CaretToolbar disabled={!enableAfterCoolDown} menu={toolbar.getCaretMenu()} onPopupClose={handleClose} />
);

const lastEventIsCaretPositionChange =
toolbar.lastEvent?.type === 'cursor' &&
typeof (toolbar.lastEvent?.detail as PeritextEventDetailMap['cursor']).at === 'number';
if (doHideForCoolDown) {
toolbarElement = <AfterTimeout ms={500}>{toolbarElement}</AfterTimeout>;
}

return (
<span className={blockClass}>
{children}
<span className={overClass} contentEditable={false}>
{lastEventIsCaretPositionChange && <CaretToolbar />}
</span>
{/* <span
className={overClass}
contentEditable={false}
>
{(showCaretToolbarValue && focus) && (toolbarElement)}
</span> */}
</span>
);
};
Loading