Skip to content

Commit

Permalink
feat: add textarea custom element
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoiver committed Jan 26, 2025
1 parent 9e4f3c0 commit ab0818c
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 23 deletions.
11 changes: 5 additions & 6 deletions packages/core/src/utils/glyph/sdf-esdt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,22 @@ const paintIntoStage = (
inner.fill(0, 0, np);

// const getData = (x: number, y: number) => data[y * w + x] ?? 0;
const getData = (x: number, y: number) => data[4 * (y * w + x) + 3] ?? 0;
// const getData = (x: number, y: number) =>
// (data[4 * (y * w + x) + 3] ?? 0) / 255;
const getData = (x: number, y: number) =>
(data[4 * (y * w + x) + 3] ?? 0) / 255;

for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const a = getData(x, y);
if (!a) continue;

const i = (y + pad) * wp + x + pad;
if (a >= 254) {

if (a >= 254 / 255) {
// Fix for bad rasterizer rounding
data[4 * (y * w + x) + 3] = 255;

outer[i] = 0;
inner[i] = INF;
} else {
} else if (a > 0) {
outer[i] = 0;
inner[i] = 0;
}
Expand Down
1 change: 1 addition & 0 deletions packages/site/docs/.vitepress/config/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const en = defineConfig({
text: 'Lesson 016 - Text advanced features',
link: 'lesson-016',
},
{ text: 'Lesson 017 - Collaborative', link: 'lesson-017' },
],
},
],
Expand Down
1 change: 1 addition & 0 deletions packages/site/docs/.vitepress/config/zh.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const zh = defineConfig({
{ text: '课程14 - 画布模式与辅助 UI', link: 'lesson-014' },
{ text: '课程15 - 绘制文本', link: 'lesson-015' },
{ text: '课程16 - 文本的高级特性', link: 'lesson-016' },
{ text: '课程17 - 协同', link: 'lesson-017' },
],
},
],
Expand Down
18 changes: 17 additions & 1 deletion packages/site/docs/zh/guide/lesson-016.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,26 @@ canvas.drawTextBlob(textblob, 0, 0, textPaint);

### 输入框 {#textarea}

目前我们只实现了文本的绘制,实际在应用中,文本输入框是必不可少的。下图来自 Figma
目前我们只实现了文本的绘制,实际在应用中,文本输入框是必不可少的。下图来自 Figma,可以看到使用了原生的 `<textarea>` 元素定位在画布上,当双击 Text 时,会展示输入框:

![textarea in figma](/textarea-in-figma.png)

在 excalidraw 中也采用了这种方式:<https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/textWysiwyg.tsx#L728>

我们也增加一个 `<ic-textarea>` 元素:

```ts
@customElement('ic-textarea')
export class Textarea extends LitElement {
@query('textarea')
editable: HTMLTextAreaElement;

render() {
return html`<textarea></textarea>`;
}
}
```

### 文本选中 {#text-selection}

## 特殊效果 {#special-effects}
Expand Down
32 changes: 16 additions & 16 deletions packages/site/docs/zh/guide/lesson-017.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,19 @@ interface CRDT<T, S> {
- [Building a Collaborative Pixel Art Editor with CRDTs]
- [Making CRDTs 98% More Efficient]

[Movable tree CRDTs and Loro's implementation](https://news.ycombinator.com/item?id=41099901)
[CRDTs: The Hard Parts](https://www.youtube.com/watch?v=x7drE24geUw)
[Peritext - A CRDT for Rich-Text Collaboration](https://www.inkandswitch.com/peritext/)
[Collaborative Text Editing with Eg-Walker](https://www.youtube.com/watch?v=rjbEG7COj7o)
[Local-first software - You own your data, in spite of the cloud](https://www.inkandswitch.com/local-first/)
[I was wrong. CRDTs are the future](https://josephg.com/blog/crdts-are-the-future/)
[5000x faster CRDTs: An Adventure in Optimization](https://josephg.com/blog/crdts-go-brrr/)
[Loro Excalidraw Example](https://github.com/loro-dev/loro-excalidraw)
[Excalidraw HistoryEntry](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/history.ts#L160-L164)
[automerge wasm](https://automerge.org/blog/2024/08/23/wasm-packaging/)
[The past, present, and future of local-first](https://speakerdeck.com/ept/the-past-present-and-future-of-local-first)
[TipTap offline support]: <https://tiptap.dev/docs/guides/offline-support>
[An Interactive Intro to CRDTs]: <https://jakelazaroff.com/words/an-interactive-intro-to-crdts/>
[Building a Collaborative Pixel Art Editor with CRDTs]: <https://jakelazaroff.com/words/building-a-collaborative-pixel-art-editor-with-crdts/>
[Making CRDTs 98% More Efficient]: <https://jakelazaroff.com/words/making-crdts-98-percent-more-efficient/>
[Learn Yjs]: <https://learn.yjs.dev/>
[Movable tree CRDTs and Loro's implementation]: https://news.ycombinator.com/item?id=41099901
[CRDTs: The Hard Parts]: https://www.youtube.com/watch?v=x7drE24geUw
[Peritext - A CRDT for Rich-Text Collaboration]: https://www.inkandswitch.com/peritext/
[Collaborative Text Editing with Eg-Walker]: https://www.youtube.com/watch?v=rjbEG7COj7o
[Local-first software - You own your data, in spite of the cloud]: https://www.inkandswitch.com/local-first/
[I was wrong. CRDTs are the future]: https://josephg.com/blog/crdts-are-the-future/
[5000x faster CRDTs: An Adventure in Optimization]: https://josephg.com/blog/crdts-go-brrr/
[Loro Excalidraw Example]: https://github.com/loro-dev/loro-excalidraw
[Excalidraw HistoryEntry]: https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/history.ts#L160-L164
[automerge wasm]: https://automerge.org/blog/2024/08/23/wasm-packaging/
[The past, present, and future of local-first]: https://speakerdeck.com/ept/the-past-present-and-future-of-local-first
[TipTap offline support]: https://tiptap.dev/docs/guides/offline-support
[An Interactive Intro to CRDTs]: https://jakelazaroff.com/words/an-interactive-intro-to-crdts/
[Building a Collaborative Pixel Art Editor with CRDTs]: https://jakelazaroff.com/words/building-a-collaborative-pixel-art-editor-with-crdts/
[Making CRDTs 98% More Efficient]: https://jakelazaroff.com/words/making-crdts-98-percent-more-efficient/
[Learn Yjs]: https://learn.yjs.dev/
1 change: 1 addition & 0 deletions packages/ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './zoom-toolbar';
export * from './mode-toolbar';
export * from './image-exporter';
export * from './property-drawer';
export * from './textarea';
1 change: 1 addition & 0 deletions packages/ui/src/infinite-canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ export class InfiniteCanvas extends LitElement {
></ic-mode-toolbar>
<ic-exporter></ic-exporter>
<ic-property-drawer></ic-property-drawer>
<ic-textarea></ic-textarea>
</sl-resize-observer>
`,
error: (e: Error) => html`<sl-alert variant="danger" open>
Expand Down
74 changes: 74 additions & 0 deletions packages/ui/src/textarea.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { html, css, LitElement } from 'lit';
import { customElement, query } from 'lit/decorators.js';
import { consume } from '@lit/context';
import { canvasContext } from './context';
import type { Canvas } from '@infinite-canvas-tutorial/core';

@customElement('ic-textarea')
export class Textarea extends LitElement {
static styles = css`
:host {
box-shadow: var(--sl-shadow-medium);
background: white;
}
`;

@query('textarea')
editable: HTMLTextAreaElement;

@consume({ context: canvasContext, subscribe: true })
canvas: Canvas;

connectedCallback() {
super.connectedCallback();
}

firstUpdated() {
const editable = this.editable;

// @see https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/textWysiwyg.tsx#L287C2-L292C48
editable.dir = 'auto';
editable.tabIndex = 0;
editable.dataset.type = 'wysiwyg';
// prevent line wrapping on Safari
editable.wrap = 'off';
editable.classList.add('ic-wysiwyg');

const whiteSpace = 'pre';
const wordBreak = 'normal';

Object.assign(editable.style, {
position: 'absolute',
display: 'inline-block',
minHeight: '1em',
backfaceVisibility: 'hidden',
margin: 0,
padding: 0,
border: 0,
outline: 0,
resize: 'none',
background: 'transparent',
overflow: 'hidden',
// must be specified because in dark mode canvas creates a stacking context
zIndex: 'var(--zIndex-wysiwyg)',
wordBreak,
// prevent line wrapping (`whitespace: nowrap` doesn't work on FF)
whiteSpace,
overflowWrap: 'break-word',
boxSizing: 'content-box',
});

// editable.value = element.originalText;
// updateWysiwygStyle();
}

render() {
return html`<textarea></textarea>`;
}
}

declare global {
interface HTMLElementTagNameMap {
'ic-textarea': Textarea;
}
}

0 comments on commit ab0818c

Please sign in to comment.