Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
10b0a30
사이트: 제품 화면에 Document·Canvas·Tree·Board를 채우고 모든 데모가 위젯 바인딩을 쓴다
Aug 20, 2026
dd8a696
어포던스: 제품 최전선을 API와 사용법으로 연다
Aug 20, 2026
88ae438
어포던스: 사용법 본문 링크와 고르기 증명을 패키지에 닫는다
Aug 20, 2026
b5358df
어포던스: 사이드 그룹을 English로 두고 키보드·마우스·커서 TBD를 채운다
Aug 20, 2026
9884988
어포던스: 선반 이름을 Select, Focus, Marquee 같은 de facto로 둔다
Aug 20, 2026
1cff18f
어포던스: TBD 사용법 명세를 pointerSelect와 같은 호출로 적는다
Aug 20, 2026
669a66c
어포던스: usage를 호스트 이벤트와 editor.dispatch의 이음으로 적는다
Aug 20, 2026
44aa004
어포던스: 같은 { hand, cursor, commit } 결과로 데모가 움직인다
Aug 20, 2026
0a998d6
어포던스: 같은 applyAffordance로 장르 증명이 움직인다
Aug 20, 2026
6430eae
어포던스: preview와 commit을 한 호출 경로로 나눈다
Aug 20, 2026
65c86cd
어포던스: mint는 commitAffordance만 하고 unwrap을 공개에서 뺀다
Aug 20, 2026
386f2aa
어포던스: 남은 제품 데모도 applyAffordance로 고른다
Aug 20, 2026
1994d67
사이트: Affordance를 최전선에 두고 Hands를 그 다음 선반으로 둔다
Aug 20, 2026
b42a8cf
테스트: Connector 문서 이동 대기를 CI 시간에 맞춘다
Aug 20, 2026
23467e0
사이트: 증명 라벨을 Hands Canvas와 겹치지 않게 둔다
Aug 20, 2026
d29d579
Merge pull request #457 from developer-1px/issue-456-affordance
developer-1px Aug 20, 2026
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
48 changes: 35 additions & 13 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,6 @@ Editing
|-- Clipboard
`-- History

Hands
|-- Document
|-- Order
|-- Object
|-- Sheet
|-- Tree
|-- Kanban
`-- Database

Adapter
|-- Keyboard
|-- Clipboard adapter
Expand All @@ -76,10 +67,41 @@ Connector
|-- Zod
`-- TanStack Table

제품 화면
|-- Toolbar
|-- Listbox
`-- Grid
Affordance
|-- Focus
|-- Caret
|-- Select
|-- Typeahead
|-- Activate
|-- Escape
|-- Expand/Collapse
|-- Undo
|-- Delete
|-- Rename
|-- Nudge
|-- Hover
|-- Double-click
|-- Triple-click
|-- Context menu
|-- Drag
|-- Marquee
|-- Drop
|-- Duplicate
|-- Resize
|-- Pan
|-- Scroll
|-- Zoom
|-- Snap
`-- Not-allowed

Hands
|-- Document
|-- Order
|-- Object
|-- Sheet
|-- Tree
|-- Kanban
`-- Database
```

## 규범 우선순위
Expand Down
6 changes: 6 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ source and release history remains available from Git commits and version tags.

## Next

- Opened Affordance as the product-facing keyboard and mouse contract, with
usage and API for select, fold, drag, and undo/redo. Live widget screens
stay as proofs, not the catalog entrance.
- 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
Expand Down
53 changes: 53 additions & 0 deletions docs/evaluate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,32 @@ const publicDocs = {
quickstart: read("docs/public/quickstart.md"),
api: read("docs/public/api.md"),
adapters: read("docs/public/adapters.md"),
affordance: read("docs/public/affordance.md"),
affordanceSelect: read("docs/public/affordance-select.md"),
affordanceFold: read("docs/public/affordance-fold.md"),
affordanceDrag: read("docs/public/affordance-drag.md"),
affordanceHistory: read("docs/public/affordance-history.md"),
affordanceFocus: read("docs/public/affordance-focus.md"),
affordanceCaret: read("docs/public/affordance-caret.md"),
affordanceTypeahead: read("docs/public/affordance-typeahead.md"),
affordanceActivate: read("docs/public/affordance-activate.md"),
affordanceCancel: read("docs/public/affordance-cancel.md"),
affordanceDelete: read("docs/public/affordance-delete.md"),
affordanceRename: read("docs/public/affordance-rename.md"),
affordanceNudge: read("docs/public/affordance-nudge.md"),
affordanceHover: read("docs/public/affordance-hover.md"),
affordanceDoubleClick: read("docs/public/affordance-double-click.md"),
affordanceTripleClick: read("docs/public/affordance-triple-click.md"),
affordanceContextMenu: read("docs/public/affordance-context-menu.md"),
affordanceMarquee: read("docs/public/affordance-marquee.md"),
affordanceDrop: read("docs/public/affordance-drop.md"),
affordanceCopyDrag: read("docs/public/affordance-copy-drag.md"),
affordanceResize: read("docs/public/affordance-resize.md"),
affordancePan: read("docs/public/affordance-pan.md"),
affordanceScroll: read("docs/public/affordance-scroll.md"),
affordanceZoom: read("docs/public/affordance-zoom.md"),
affordanceSnap: read("docs/public/affordance-snap.md"),
affordanceForbid: read("docs/public/affordance-forbid.md"),
connectors: read("docs/public/connectors.md"),
reactEditing: read("docs/public/react-editing.md"),
collaboration: read("docs/public/collaboration.md"),
Expand Down Expand Up @@ -95,6 +121,7 @@ const activeCompanionPackages = new Set([
"@interactive-os/json-document-react",
"@interactive-os/json-document-react-hook-form",
"@interactive-os/json-document-ajv",
"@interactive-os/json-document-affordance",
"@interactive-os/json-document-zod",
"@interactive-os/json-document-tanstack-table",
"@interactive-os/json-document-web",
Expand All @@ -105,6 +132,32 @@ const activeCompanionPackages = new Set([

if (JSON.stringify(fileNames("docs/public")) !== JSON.stringify([
"adapters.md",
"affordance-activate.md",
"affordance-cancel.md",
"affordance-caret.md",
"affordance-context-menu.md",
"affordance-copy-drag.md",
"affordance-delete.md",
"affordance-double-click.md",
"affordance-drag.md",
"affordance-drop.md",
"affordance-focus.md",
"affordance-fold.md",
"affordance-forbid.md",
"affordance-history.md",
"affordance-hover.md",
"affordance-marquee.md",
"affordance-nudge.md",
"affordance-pan.md",
"affordance-rename.md",
"affordance-resize.md",
"affordance-scroll.md",
"affordance-select.md",
"affordance-snap.md",
"affordance-triple-click.md",
"affordance-typeahead.md",
"affordance-zoom.md",
"affordance.md",
"api.md",
"clipboard.md",
"collaboration-history.md",
Expand Down
36 changes: 36 additions & 0 deletions docs/public/affordance-activate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Activate

TBD.

Activate는 대상의 기본 동작을 실행하는 손입니다. `pointer` 커서가 이
손을 가리킵니다. Enter와 기본 클릭은 같은 손입니다. Listbox에서 Space는
[Select](affordance-select.md)의 toggle입니다.

```ts
import {
activateAffordance,
resolveAffordanceKey,
} from "@interactive-os/json-document-affordance";

function onPointerDown(event: PointerEvent, itemId: string) {
if (activateAffordance(event) === "activate") hostOpen(itemId);
}

function onKeyDown(event: KeyboardEvent) {
if (activateAffordance(event) === "activate") hostOpen(focusKey);
const command = resolveAffordanceKey(event);
if (command?.type === "toggle") {
editor.dispatch({ type: "selection.set", itemId: focusKey, mode: "toggle" });
}
}
```

호스트는 기본 동작이 무엇인지 정합니다. 목록 toggle은 json-document 선택으로,
열기는 호스트 Intent로 갑니다.

닫는 손:
- 기본 클릭 (`detail` 1)
- Enter
- Space는 장르 Intent: 버튼은 activate, 목록은 toggle

근거: [APG Button](https://www.w3.org/WAI/ARIA/apg/patterns/button/), [CSS UI cursor `pointer`](https://www.w3.org/TR/css-ui-4/#cursor)
40 changes: 40 additions & 0 deletions docs/public/affordance-cancel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Escape

TBD.

Escape는 진행 중인 손과 임시 UI를 버리는 손입니다. Escape 키는 메뉴·대화·
드래그를 닫고, `pointercancel`은 포인터 제스처를 중단합니다.

```ts
import {
applyAffordance,
escapeAffordance,
} from "@interactive-os/json-document-affordance";

function onKeyDown(event: KeyboardEvent) {
applyAffordance(escapeAffordance(event), {
hand: (hand) => {
if (hand.type !== "cancel") return;
setMarquee(null);
setDrag(null);
},
});
}

function onPointerCancel(event: PointerEvent) {
applyAffordance(escapeAffordance(event), {
hand: (hand) => {
if (hand.type === "cancel") setDrag(null);
},
});
}
```

호스트는 무엇이 열려 있는지를 가집니다. 버리는 손은 보통 호스트 화면
상태입니다. 문서 값은 바꾸지 않습니다.

닫는 손:
- Escape: 메뉴, 대화, 드래그, Rename 중단
- pointercancel / lostpointercapture: 포인터 제스처 중단

근거: [APG Dialog](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/), [Pointer Events](https://www.w3.org/TR/pointerevents/)
48 changes: 48 additions & 0 deletions docs/public/affordance-caret.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Caret

TBD.

Caret은 글 안의 삽입점입니다. 항목 [Select](affordance-select.md)와 다릅니다.
`text` / `vertical-text` 커서가 이 손을 가리킵니다. 클릭은 삽입점을 두고,
드래그는 글 범위를 고릅니다.

```ts
import { caretAffordance, caretCursor } from "@interactive-os/json-document-affordance";

function onPointerMove(event: PointerEvent) {
event.currentTarget.style.cursor = caretCursor("horizontal");
}

function onPointerDown(event: PointerEvent, blockId: string) {
const hand = caretAffordance({ type: "pointer", detail: event.detail });
if (hand.type === "place") {
editor.dispatch({
type: "selection.set",
blockId,
offset: hostHitOffset(event),
});
}
}

function onKeyDown(event: KeyboardEvent) {
const hand = caretAffordance(event);
if (hand?.type === "move") {
editor.dispatch({
type: "selection.set",
blockId: focus.blockId,
offset: hostMoveOffset(focus.offset, hand),
mode: hand.operation,
});
}
}
```

호스트는 I-beam과 글 기하를 그립니다. 값 삽입은 Hands입니다.

닫는 손:
- 클릭: 삽입점
- 드래그: 글 범위
- 화살표 / Home / End: 글자·줄 끝
- IME composition은 삽입 경로

근거: [CSS UI cursor `text`](https://www.w3.org/TR/css-ui-4/#cursor), [UI Events select](https://www.w3.org/TR/uievents/)
33 changes: 33 additions & 0 deletions docs/public/affordance-context-menu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Context menu

TBD.

Context menu는 대상이 있는 자리에서 보조 동작을 여는 손입니다.
`context-menu` 커서가 이 손을 가리킵니다.

```ts
import { contextMenuAffordance } from "@interactive-os/json-document-affordance";

function onContextMenu(event: MouseEvent, itemId: string) {
if (contextMenuAffordance(event) !== "open") return;
event.preventDefault();
setMenu({ itemId, x: event.clientX, y: event.clientY });
}

function onKeyDown(event: KeyboardEvent) {
if (contextMenuAffordance(event) === "open") {
setMenu({ itemId: focusKey, x: 0, y: 0 });
}
if (contextMenuAffordance(event) === "cancel") setMenu(null);
}
```

호스트는 메뉴 항목을 그립니다. 여는 손은 호스트 화면 상태입니다.

닫는 손:
- 보조 버튼 / `contextmenu` / `auxclick`
- Shift+F10
- Menu 키
- Escape로 [Escape](affordance-cancel.md)

근거: [Pointer Events contextmenu](https://www.w3.org/TR/pointerevents/), [CSS UI cursor `context-menu`](https://www.w3.org/TR/css-ui-4/#cursor)
67 changes: 67 additions & 0 deletions docs/public/affordance-copy-drag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Duplicate

TBD.

Duplicate는 수정 키를 누른 채 드래그하면 원본을 복제하는 손입니다.
`copy` / `alias` 커서가 이 손을 가리킵니다.

```ts
import {
applyAffordance,
commitAffordance,
dragAffordance,
dragOperation,
dropAffordance,
} from "@interactive-os/json-document-affordance";

function onPointerMove(event: PointerEvent) {
applyAffordance(dragOperation(event), {
cursor: (cursor) => {
event.currentTarget.style.cursor = cursor;
},
});
}

function onPointerUp(event: PointerEvent) {
const committed = commitAffordance(
dragAffordance(origin, { x: event.clientX, y: event.clientY }),
);
if (!committed) return;
applyAffordance(committed, {
commit: (translate) => {
if (translate.type !== "translate") return;
let copied = false;
applyAffordance(dragOperation(event), {
hand: (operation) => {
if (operation.type !== "copy") return;
copied = true;
hostDuplicate(objectIds, translate);
},
});
if (copied) return;
const drop = commitAffordance(dropAffordance({ canDrop: true }));
if (!drop) return;
applyAffordance(drop, {
commit: (hand) => {
if (hand.type !== "move-drop") return;
editor.dispatch({
type: "object.translate",
objectIds,
dx: translate.dx,
dy: translate.dy,
});
},
});
},
});
}
```

복제 생성은 장르 Intent(호스트 또는 editor)이고, 옮기기는 json-document로
갑니다. 값의 클립보드 복사/붙이기는 Hands입니다.

닫는 손:
- Alt/Option + 드래그 → copy
- 커서 `copy` 또는 `alias`

근거: [Apple HIG pointing devices](https://developer.apple.com/design/human-interface-guidelines/pointing-devices), [CSS UI cursor `copy` / `alias`](https://www.w3.org/TR/css-ui-4/#cursor)
Loading