From 10b0a306664a90ae8dd05611e2df29cf3ac4362c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B2=E1=84=8B=E1=85=AD=E1=86=BC=E1=84=90?= =?UTF-8?q?=E1=85=A2?= Date: Thu, 20 Aug 2026 20:34:31 +0900 Subject: [PATCH 01/15] =?UTF-8?q?=EC=82=AC=EC=9D=B4=ED=8A=B8:=20=EC=A0=9C?= =?UTF-8?q?=ED=92=88=20=ED=99=94=EB=A9=B4=EC=97=90=20Document=C2=B7Canvas?= =?UTF-8?q?=C2=B7Tree=C2=B7Board=EB=A5=BC=20=EC=B1=84=EC=9A=B0=EA=B3=A0=20?= =?UTF-8?q?=EB=AA=A8=EB=93=A0=20=EB=8D=B0=EB=AA=A8=EA=B0=80=20=EC=9C=84?= =?UTF-8?q?=EC=A0=AF=20=EB=B0=94=EC=9D=B8=EB=94=A9=EC=9D=84=20=EC=93=B4?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Toolbar/Listbox/Grid 바인딩을 장르 editor 타입에서 분리하고, 빠진 네 장르 위젯을 카탈로그에 연다. Database는 Grid를 그대로 쓴다. --- docs/README.md | 6 +- docs/changelog.md | 3 + docs/public/overview.md | 4 +- site/site-routes.json | 30 +++- site/src/app/routeTree.gen.ts | 84 +++++++++++ site/src/app/routes/_page/widgets/board.tsx | 6 + site/src/app/routes/_page/widgets/canvas.tsx | 6 + .../src/app/routes/_page/widgets/document.tsx | 6 + site/src/app/routes/_page/widgets/tree.tsx | 6 + site/src/app/site-layers.ts | 2 +- .../clipboard/ClipboardAdapterLab.tsx | 5 +- .../adapters/keyboard/KeyboardAdapterLab.tsx | 5 +- .../routes/canvas-demo/CanvasDemoRoute.tsx | 6 +- .../ReactHookFormConnectorLab.tsx | 6 +- .../connectors/react/ReactConnectorLab.tsx | 10 +- .../TanStackTableConnectorLab.tsx | 13 +- .../src/routes/connectors/zod/ZodAdminLab.tsx | 13 +- .../database-demo/DatabaseTableDemo.tsx | 13 +- .../document-demo/DocumentDemoRoute.tsx | 10 +- .../editing-demos/ClipboardDemoRoute.tsx | 5 +- .../routes/editing-demos/HistoryDemoRoute.tsx | 10 +- .../editing-demos/SelectionDemoRoute.tsx | 5 +- .../editing-demos/TopologyDemoRoute.tsx | 5 +- site/src/routes/home/HomeRoute.tsx | 2 +- .../routes/kanban-demo/KanbanDemoRoute.tsx | 14 +- .../routes/object-demo/ObjectDemoRoute.tsx | 10 +- site/src/routes/order-demo/OrderDemoRoute.tsx | 10 +- .../rich-text-demo/RichTextDemoRoute.tsx | 8 +- site/src/routes/sheet-demo/SheetDemo.tsx | 13 +- site/src/routes/tree-demo/TreeDemoRoute.tsx | 10 +- site/src/routes/widgets/BoardWidgetRoute.tsx | 84 +++++++++++ site/src/routes/widgets/CanvasWidgetRoute.tsx | 67 +++++++++ .../routes/widgets/DocumentWidgetRoute.tsx | 88 ++++++++++++ site/src/routes/widgets/GridWidgetRoute.tsx | 80 +++++++++-- .../src/routes/widgets/ListboxWidgetRoute.tsx | 49 +++++-- .../src/routes/widgets/ToolbarWidgetRoute.tsx | 52 +++++-- site/src/routes/widgets/TreeWidgetRoute.tsx | 136 ++++++++++++++++++ .../src/routes/widgets/WidgetCatalogRoute.tsx | 18 ++- site/src/routes/widgets/binding/index.ts | 5 - site/src/routes/widgets/binding/order.ts | 66 --------- site/src/routes/widgets/binding/sheet.ts | 84 ----------- .../widget-binding}/history.ts | 0 site/src/shared/widget-binding/index.ts | 4 + .../widget-binding}/keyboard.ts | 0 .../widget-binding}/option.ts | 7 + site/tests/browser/site-shell.spec.ts | 10 +- site/tests/browser/widgets.spec.ts | 46 ++++++ site/tests/unit/app-shell.test.tsx | 2 +- site/tests/unit/breadcrumb.test.ts | 5 + site/tests/unit/widget-binding.test.ts | 31 +++- 50 files changed, 872 insertions(+), 288 deletions(-) create mode 100644 site/src/app/routes/_page/widgets/board.tsx create mode 100644 site/src/app/routes/_page/widgets/canvas.tsx create mode 100644 site/src/app/routes/_page/widgets/document.tsx create mode 100644 site/src/app/routes/_page/widgets/tree.tsx create mode 100644 site/src/routes/widgets/BoardWidgetRoute.tsx create mode 100644 site/src/routes/widgets/CanvasWidgetRoute.tsx create mode 100644 site/src/routes/widgets/DocumentWidgetRoute.tsx create mode 100644 site/src/routes/widgets/TreeWidgetRoute.tsx delete mode 100644 site/src/routes/widgets/binding/index.ts delete mode 100644 site/src/routes/widgets/binding/order.ts delete mode 100644 site/src/routes/widgets/binding/sheet.ts rename site/src/{routes/widgets/binding => shared/widget-binding}/history.ts (100%) create mode 100644 site/src/shared/widget-binding/index.ts rename site/src/{routes/widgets/binding => shared/widget-binding}/keyboard.ts (100%) rename site/src/{routes/widgets/binding => shared/widget-binding}/option.ts (77%) diff --git a/docs/README.md b/docs/README.md index 37b0e180..231c9e69 100644 --- a/docs/README.md +++ b/docs/README.md @@ -79,7 +79,11 @@ Connector 제품 화면 |-- Toolbar |-- Listbox -`-- Grid +|-- Grid +|-- Document +|-- Canvas +|-- Tree +`-- Board ``` ## 규범 우선순위 diff --git a/docs/changelog.md b/docs/changelog.md index 7f290d3c..68022853 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -5,6 +5,9 @@ source and release history remains available from Git commits and version tags. ## Next +- Opened Document, Canvas, Tree, and Board on 제품 화면, and made Toolbar, + Listbox, Grid, and those widgets share prop bindings that live demos + consume without forking. Database stays on Grid. - Rewrote Why as the background of the editing-platform kernel: the same JSON Document port for Collaboration, session state in Editing, Hands as the genre shelf, and Adapter, Connector, and 제품 화면 as diff --git a/docs/public/overview.md b/docs/public/overview.md index e0d43c04..304955d6 100644 --- a/docs/public/overview.md +++ b/docs/public/overview.md @@ -88,8 +88,8 @@ React로 그리거나 Zod로 검사하려면 이름 있는 라이브러리의 계약에 맞춰야 합니다. Connector가 그 일을 합니다. document 변경은 React 구독으로 흐르고, 표의 보이는 행과 열은 Sheet의 Topology가 됩니다. -툴바와 리스트박스, 격자는 제품이 그리는 화면입니다. 편집 상태를 읽어 -되돌리기 버튼을 끄거나, 고른 칸을 칠합니다. +툴바, 리스트박스, 격자, 문서 줄, 캔버스, 나무, 보드는 제품이 그리는 +화면입니다. 편집 상태를 읽어 되돌리기 버튼을 끄거나, 고른 칸을 칠합니다. 플랫폼 계약과 라이브러리 연결과 호스트 화면은 높이가 같고 하는 일만 다릅니다. 필요한 것만 고릅니다. [Adapters](adapters.md)와 diff --git a/site/site-routes.json b/site/site-routes.json index 8c1b0517..08468156 100644 --- a/site/site-routes.json +++ b/site/site-routes.json @@ -437,7 +437,7 @@ "path": "/widgets", "label": "제품 화면", "title": "Widgets - json-document", - "description": "Sample widgets that bind only editing affordances: Toolbar, Listbox, and Grid.", + "description": "Sample widgets that bind only editing affordances: Toolbar, Listbox, Grid, Document, Canvas, Tree, and Board.", "navigationGroup": "제품 화면", "sidebar": false }, @@ -461,5 +461,33 @@ "title": "Grid Widget - json-document", "description": "A grid reads visible topology and selected cells from Sheet selection.", "navigationGroup": "제품 화면" + }, + { + "path": "/widgets/document", + "label": "Document", + "title": "Document Widget - json-document", + "description": "A document line reads selected keys, focus, and text offset from block selection.", + "navigationGroup": "제품 화면" + }, + { + "path": "/widgets/canvas", + "label": "Canvas", + "title": "Canvas Widget - json-document", + "description": "A canvas reads selected objects on a plane from Object selection.", + "navigationGroup": "제품 화면" + }, + { + "path": "/widgets/tree", + "label": "Tree", + "title": "Tree Widget - json-document", + "description": "A tree reads the visible hierarchical line from Tree selection.", + "navigationGroup": "제품 화면" + }, + { + "path": "/widgets/board", + "label": "Board", + "title": "Board Widget - json-document", + "description": "A board reads columns of cards and selected keys from Kanban selection.", + "navigationGroup": "제품 화면" } ] diff --git a/site/src/app/routeTree.gen.ts b/site/src/app/routeTree.gen.ts index 9bdf6ba1..d80d82c0 100644 --- a/site/src/app/routeTree.gen.ts +++ b/site/src/app/routeTree.gen.ts @@ -54,9 +54,13 @@ import { Route as PageDocsTreeRouteImport } from "./routes/_page/docs/tree"; import { Route as PageDocsTutorialRouteImport } from "./routes/_page/docs/tutorial"; import { Route as PageEditingRichTextRouteImport } from "./routes/_page/editing/rich-text"; import { Route as PageWidgetsIndexRouteImport } from "./routes/_page/widgets/index"; +import { Route as PageWidgetsBoardRouteImport } from "./routes/_page/widgets/board"; +import { Route as PageWidgetsCanvasRouteImport } from "./routes/_page/widgets/canvas"; +import { Route as PageWidgetsDocumentRouteImport } from "./routes/_page/widgets/document"; import { Route as PageWidgetsGridRouteImport } from "./routes/_page/widgets/grid"; import { Route as PageWidgetsListboxRouteImport } from "./routes/_page/widgets/listbox"; import { Route as PageWidgetsToolbarRouteImport } from "./routes/_page/widgets/toolbar"; +import { Route as PageWidgetsTreeRouteImport } from "./routes/_page/widgets/tree"; import { Route as PageConnectorsZodIndexRouteImport } from "./routes/_page/connectors/zod/index"; import { Route as PageConnectorsZodValidateRouteImport } from "./routes/_page/connectors/zod/validate"; import { Route as PageDocsCollaborationIndexRouteImport } from "./routes/_page/docs/collaboration/index"; @@ -294,6 +298,21 @@ const PageWidgetsIndexRoute = PageWidgetsIndexRouteImport.update({ path: "/widgets/", getParentRoute: () => PageRoute, } as any); +const PageWidgetsBoardRoute = PageWidgetsBoardRouteImport.update({ + id: "/widgets/board", + path: "/widgets/board", + getParentRoute: () => PageRoute, +} as any); +const PageWidgetsCanvasRoute = PageWidgetsCanvasRouteImport.update({ + id: "/widgets/canvas", + path: "/widgets/canvas", + getParentRoute: () => PageRoute, +} as any); +const PageWidgetsDocumentRoute = PageWidgetsDocumentRouteImport.update({ + id: "/widgets/document", + path: "/widgets/document", + getParentRoute: () => PageRoute, +} as any); const PageWidgetsGridRoute = PageWidgetsGridRouteImport.update({ id: "/widgets/grid", path: "/widgets/grid", @@ -309,6 +328,11 @@ const PageWidgetsToolbarRoute = PageWidgetsToolbarRouteImport.update({ path: "/widgets/toolbar", getParentRoute: () => PageRoute, } as any); +const PageWidgetsTreeRoute = PageWidgetsTreeRouteImport.update({ + id: "/widgets/tree", + path: "/widgets/tree", + getParentRoute: () => PageRoute, +} as any); const PageConnectorsZodIndexRoute = PageConnectorsZodIndexRouteImport.update({ id: "/connectors/zod/", path: "/connectors/zod/", @@ -397,9 +421,13 @@ export interface FileRoutesByFullPath { "/docs/tree": typeof PageDocsTreeRoute; "/docs/tutorial": typeof PageDocsTutorialRoute; "/editing/rich-text": typeof PageEditingRichTextRoute; + "/widgets/board": typeof PageWidgetsBoardRoute; + "/widgets/canvas": typeof PageWidgetsCanvasRoute; + "/widgets/document": typeof PageWidgetsDocumentRoute; "/widgets/grid": typeof PageWidgetsGridRoute; "/widgets/listbox": typeof PageWidgetsListboxRoute; "/widgets/toolbar": typeof PageWidgetsToolbarRoute; + "/widgets/tree": typeof PageWidgetsTreeRoute; "/adapters/": typeof PageAdaptersIndexRoute; "/connectors/": typeof PageConnectorsIndexRoute; "/demo/": typeof PageDemoIndexRoute; @@ -454,9 +482,13 @@ export interface FileRoutesByTo { "/docs/tree": typeof PageDocsTreeRoute; "/docs/tutorial": typeof PageDocsTutorialRoute; "/editing/rich-text": typeof PageEditingRichTextRoute; + "/widgets/board": typeof PageWidgetsBoardRoute; + "/widgets/canvas": typeof PageWidgetsCanvasRoute; + "/widgets/document": typeof PageWidgetsDocumentRoute; "/widgets/grid": typeof PageWidgetsGridRoute; "/widgets/listbox": typeof PageWidgetsListboxRoute; "/widgets/toolbar": typeof PageWidgetsToolbarRoute; + "/widgets/tree": typeof PageWidgetsTreeRoute; "/adapters": typeof PageAdaptersIndexRoute; "/connectors": typeof PageConnectorsIndexRoute; "/demo": typeof PageDemoIndexRoute; @@ -513,9 +545,13 @@ export interface FileRoutesById { "/_page/docs/tree": typeof PageDocsTreeRoute; "/_page/docs/tutorial": typeof PageDocsTutorialRoute; "/_page/editing/rich-text": typeof PageEditingRichTextRoute; + "/_page/widgets/board": typeof PageWidgetsBoardRoute; + "/_page/widgets/canvas": typeof PageWidgetsCanvasRoute; + "/_page/widgets/document": typeof PageWidgetsDocumentRoute; "/_page/widgets/grid": typeof PageWidgetsGridRoute; "/_page/widgets/listbox": typeof PageWidgetsListboxRoute; "/_page/widgets/toolbar": typeof PageWidgetsToolbarRoute; + "/_page/widgets/tree": typeof PageWidgetsTreeRoute; "/_page/adapters/": typeof PageAdaptersIndexRoute; "/_page/connectors/": typeof PageConnectorsIndexRoute; "/_page/demo/": typeof PageDemoIndexRoute; @@ -572,9 +608,13 @@ export interface FileRouteTypes { | "/docs/tree" | "/docs/tutorial" | "/editing/rich-text" + | "/widgets/board" + | "/widgets/canvas" + | "/widgets/document" | "/widgets/grid" | "/widgets/listbox" | "/widgets/toolbar" + | "/widgets/tree" | "/adapters/" | "/connectors/" | "/demo/" @@ -629,9 +669,13 @@ export interface FileRouteTypes { | "/docs/tree" | "/docs/tutorial" | "/editing/rich-text" + | "/widgets/board" + | "/widgets/canvas" + | "/widgets/document" | "/widgets/grid" | "/widgets/listbox" | "/widgets/toolbar" + | "/widgets/tree" | "/adapters" | "/connectors" | "/demo" @@ -687,9 +731,13 @@ export interface FileRouteTypes { | "/_page/docs/tree" | "/_page/docs/tutorial" | "/_page/editing/rich-text" + | "/_page/widgets/board" + | "/_page/widgets/canvas" + | "/_page/widgets/document" | "/_page/widgets/grid" | "/_page/widgets/listbox" | "/_page/widgets/toolbar" + | "/_page/widgets/tree" | "/_page/adapters/" | "/_page/connectors/" | "/_page/demo/" @@ -1027,6 +1075,27 @@ declare module "@tanstack/react-router" { preLoaderRoute: typeof PageWidgetsIndexRouteImport; parentRoute: typeof PageRoute; }; + "/_page/widgets/board": { + id: "/_page/widgets/board"; + path: "/widgets/board"; + fullPath: "/widgets/board"; + preLoaderRoute: typeof PageWidgetsBoardRouteImport; + parentRoute: typeof PageRoute; + }; + "/_page/widgets/canvas": { + id: "/_page/widgets/canvas"; + path: "/widgets/canvas"; + fullPath: "/widgets/canvas"; + preLoaderRoute: typeof PageWidgetsCanvasRouteImport; + parentRoute: typeof PageRoute; + }; + "/_page/widgets/document": { + id: "/_page/widgets/document"; + path: "/widgets/document"; + fullPath: "/widgets/document"; + preLoaderRoute: typeof PageWidgetsDocumentRouteImport; + parentRoute: typeof PageRoute; + }; "/_page/widgets/grid": { id: "/_page/widgets/grid"; path: "/widgets/grid"; @@ -1048,6 +1117,13 @@ declare module "@tanstack/react-router" { preLoaderRoute: typeof PageWidgetsToolbarRouteImport; parentRoute: typeof PageRoute; }; + "/_page/widgets/tree": { + id: "/_page/widgets/tree"; + path: "/widgets/tree"; + fullPath: "/widgets/tree"; + preLoaderRoute: typeof PageWidgetsTreeRouteImport; + parentRoute: typeof PageRoute; + }; "/_page/connectors/zod/": { id: "/_page/connectors/zod/"; path: "/connectors/zod"; @@ -1146,9 +1222,13 @@ interface PageRouteChildren { PageDocsTreeRoute: typeof PageDocsTreeRoute; PageDocsTutorialRoute: typeof PageDocsTutorialRoute; PageEditingRichTextRoute: typeof PageEditingRichTextRoute; + PageWidgetsBoardRoute: typeof PageWidgetsBoardRoute; + PageWidgetsCanvasRoute: typeof PageWidgetsCanvasRoute; + PageWidgetsDocumentRoute: typeof PageWidgetsDocumentRoute; PageWidgetsGridRoute: typeof PageWidgetsGridRoute; PageWidgetsListboxRoute: typeof PageWidgetsListboxRoute; PageWidgetsToolbarRoute: typeof PageWidgetsToolbarRoute; + PageWidgetsTreeRoute: typeof PageWidgetsTreeRoute; PageAdaptersIndexRoute: typeof PageAdaptersIndexRoute; PageConnectorsIndexRoute: typeof PageConnectorsIndexRoute; PageDemoIndexRoute: typeof PageDemoIndexRoute; @@ -1203,9 +1283,13 @@ const PageRouteChildren: PageRouteChildren = { PageDocsTreeRoute: PageDocsTreeRoute, PageDocsTutorialRoute: PageDocsTutorialRoute, PageEditingRichTextRoute: PageEditingRichTextRoute, + PageWidgetsBoardRoute: PageWidgetsBoardRoute, + PageWidgetsCanvasRoute: PageWidgetsCanvasRoute, + PageWidgetsDocumentRoute: PageWidgetsDocumentRoute, PageWidgetsGridRoute: PageWidgetsGridRoute, PageWidgetsListboxRoute: PageWidgetsListboxRoute, PageWidgetsToolbarRoute: PageWidgetsToolbarRoute, + PageWidgetsTreeRoute: PageWidgetsTreeRoute, PageAdaptersIndexRoute: PageAdaptersIndexRoute, PageConnectorsIndexRoute: PageConnectorsIndexRoute, PageDemoIndexRoute: PageDemoIndexRoute, diff --git a/site/src/app/routes/_page/widgets/board.tsx b/site/src/app/routes/_page/widgets/board.tsx new file mode 100644 index 00000000..c2fdcdfc --- /dev/null +++ b/site/src/app/routes/_page/widgets/board.tsx @@ -0,0 +1,6 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { BoardWidgetRoute } from "../../../../routes/widgets/BoardWidgetRoute"; + +export const Route = createFileRoute("/_page/widgets/board")({ + component: BoardWidgetRoute, +}); diff --git a/site/src/app/routes/_page/widgets/canvas.tsx b/site/src/app/routes/_page/widgets/canvas.tsx new file mode 100644 index 00000000..75a549b8 --- /dev/null +++ b/site/src/app/routes/_page/widgets/canvas.tsx @@ -0,0 +1,6 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { CanvasWidgetRoute } from "../../../../routes/widgets/CanvasWidgetRoute"; + +export const Route = createFileRoute("/_page/widgets/canvas")({ + component: CanvasWidgetRoute, +}); diff --git a/site/src/app/routes/_page/widgets/document.tsx b/site/src/app/routes/_page/widgets/document.tsx new file mode 100644 index 00000000..85aab7ab --- /dev/null +++ b/site/src/app/routes/_page/widgets/document.tsx @@ -0,0 +1,6 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { DocumentWidgetRoute } from "../../../../routes/widgets/DocumentWidgetRoute"; + +export const Route = createFileRoute("/_page/widgets/document")({ + component: DocumentWidgetRoute, +}); diff --git a/site/src/app/routes/_page/widgets/tree.tsx b/site/src/app/routes/_page/widgets/tree.tsx new file mode 100644 index 00000000..b64dbf01 --- /dev/null +++ b/site/src/app/routes/_page/widgets/tree.tsx @@ -0,0 +1,6 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { TreeWidgetRoute } from "../../../../routes/widgets/TreeWidgetRoute"; + +export const Route = createFileRoute("/_page/widgets/tree")({ + component: TreeWidgetRoute, +}); diff --git a/site/src/app/site-layers.ts b/site/src/app/site-layers.ts index b247a495..376ba0fb 100644 --- a/site/src/app/site-layers.ts +++ b/site/src/app/site-layers.ts @@ -14,7 +14,7 @@ export const siteLayers: ReadonlyArray = [ { group: "Hands", path: "/editors", label: "Hands", blurb: "Genre hands" }, { group: "Adapter", path: "/adapters", label: "Adapter", blurb: "Platform contracts" }, { group: "Connector", path: "/connectors", label: "Connector", blurb: "Library ecosystems" }, - { group: "제품 화면", path: "/widgets", label: "제품 화면", blurb: "Toolbar, Listbox, and Grid" }, + { group: "제품 화면", path: "/widgets", label: "제품 화면", blurb: "Toolbar, Listbox, Grid, Document, Canvas, Tree, and Board" }, ]; export const siteLayerGroups: ReadonlyArray = siteLayers.map((layer) => layer.group); diff --git a/site/src/routes/adapters/clipboard/ClipboardAdapterLab.tsx b/site/src/routes/adapters/clipboard/ClipboardAdapterLab.tsx index d97745d4..45b9992e 100644 --- a/site/src/routes/adapters/clipboard/ClipboardAdapterLab.tsx +++ b/site/src/routes/adapters/clipboard/ClipboardAdapterLab.tsx @@ -13,6 +13,7 @@ import { import { Inspector } from "../../../shared/ui/inspector"; import { SelectableItem } from "../../../shared/ui/interactive"; import { classes, ui } from "../../../shared/ui/styles"; +import { optionProps } from "../../../shared/widget-binding"; const initialDocument: BlockDocument = { blocks: [ @@ -82,11 +83,9 @@ export function ClipboardAdapterLab() {