@erclx/canon 5.26.0 → 5.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/SKILL.md +2 -0
- package/claude/skills/auto-ship/SKILL.md +3 -11
- package/claude/skills/auto-ship/references/review-findings.md +20 -0
- package/claude/skills/backlog-triage/SKILL.md +2 -0
- package/claude/skills/bash-cli-script/SKILL.md +2 -0
- package/claude/skills/build-in-slices/SKILL.md +2 -0
- package/claude/skills/canon-cli/SKILL.md +2 -0
- package/claude/skills/canon-feedback/SKILL.md +2 -0
- package/claude/skills/canon-feedback-triage/SKILL.md +2 -0
- package/claude/skills/canon-operator/SKILL.md +2 -0
- package/claude/skills/canon-rollout/SKILL.md +3 -11
- package/claude/skills/canon-rollout/references/failure-recovery.md +20 -0
- package/claude/skills/canvas/SKILL.md +13 -0
- package/claude/skills/ci-workflow/SKILL.md +2 -0
- package/claude/skills/code-craft/SKILL.md +2 -0
- package/claude/skills/codebase-layout/SKILL.md +2 -0
- package/claude/skills/context-fold/SKILL.md +3 -17
- package/claude/skills/context-fold/references/completion-report.md +26 -0
- package/claude/skills/create-rule/SKILL.md +2 -0
- package/claude/skills/create-skill/SKILL.md +2 -0
- package/claude/skills/create-standard/SKILL.md +2 -0
- package/claude/skills/decision-escalate/SKILL.md +2 -0
- package/claude/skills/deploy-app/SKILL.md +2 -0
- package/claude/skills/deprecation-migration/SKILL.md +2 -0
- package/claude/skills/design-extract/SKILL.md +2 -0
- package/claude/skills/design-taste/SKILL.md +2 -0
- package/claude/skills/docs-sync/SKILL.md +2 -0
- package/claude/skills/document-health/SKILL.md +2 -0
- package/claude/skills/draft-and-pick/SKILL.md +2 -0
- package/claude/skills/draft-doc/SKILL.md +2 -0
- package/claude/skills/draft-doc/references/wiki.md +1 -1
- package/claude/skills/draft-figure/SKILL.md +2 -0
- package/claude/skills/draft-identity/SKILL.md +2 -0
- package/claude/skills/draft-ready/SKILL.md +5 -1
- package/claude/skills/draft-screencast/SKILL.md +2 -0
- package/claude/skills/draft-slides/SKILL.md +2 -0
- package/claude/skills/git-branch/SKILL.md +2 -0
- package/claude/skills/git-commit/SKILL.md +2 -0
- package/claude/skills/git-followup/SKILL.md +2 -0
- package/claude/skills/git-issue/SKILL.md +2 -0
- package/claude/skills/git-pr/SKILL.md +2 -0
- package/claude/skills/git-pr/references/evidence.md +7 -7
- package/claude/skills/git-ship/SKILL.md +2 -0
- package/claude/skills/git-split/SKILL.md +2 -0
- package/claude/skills/git-stage/SKILL.md +2 -0
- package/claude/skills/git-worktree/SKILL.md +2 -0
- package/claude/skills/index-lookup/SKILL.md +2 -0
- package/claude/skills/markdown-propose/SKILL.md +2 -0
- package/claude/skills/memory-capture/SKILL.md +2 -0
- package/claude/skills/memory-review/SKILL.md +2 -0
- package/claude/skills/plan-feature/SKILL.md +2 -0
- package/claude/skills/plan-groundwork/SKILL.md +2 -0
- package/claude/skills/plan-intake/SKILL.md +2 -0
- package/claude/skills/plan-intake-answer/SKILL.md +2 -0
- package/claude/skills/project-commands/SKILL.md +2 -0
- package/claude/skills/read-frames/SKILL.md +2 -0
- package/claude/skills/record-screencast/SKILL.md +2 -0
- package/claude/skills/repo-metadata/SKILL.md +2 -0
- package/claude/skills/restate-plainly/SKILL.md +2 -0
- package/claude/skills/review-address/REQUIREMENT.md +4 -2
- package/claude/skills/review-address/SKILL.md +19 -14
- package/claude/skills/review-address/references/post-review-findings.md +16 -0
- package/claude/skills/review-address/references/rebase-conflicts.md +4 -2
- package/claude/skills/review-branch/SKILL.md +2 -0
- package/claude/skills/review-craft/SKILL.md +2 -0
- package/claude/skills/review-craft/references/ui.md +6 -6
- package/claude/skills/review-pr/SKILL.md +4 -8
- package/claude/skills/review-pr/references/body-variants.md +52 -0
- package/claude/skills/review-pr/references/key-changes.md +16 -0
- package/claude/skills/review-pr/references/post.md +15 -43
- package/claude/skills/review-ui/REQUIREMENT.md +2 -2
- package/claude/skills/review-ui/SKILL.md +16 -12
- package/claude/skills/review-ui/references/driving.md +8 -8
- package/claude/skills/review-ui/references/post.md +13 -3
- package/claude/skills/review-ui/references/tick.md +22 -0
- package/claude/skills/role-orchestrator/REQUIREMENT.md +15 -15
- package/claude/skills/role-orchestrator/SKILL.md +31 -42
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +18 -16
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +13 -3
- package/claude/skills/role-orchestrator/references/orchestrator-inbound.md +23 -0
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +6 -34
- package/claude/skills/role-orchestrator/references/orchestrator-output.md +4 -4
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +5 -5
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +12 -23
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +6 -6
- package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +38 -0
- package/claude/skills/role-orchestrator/references/orchestrator-sweep.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-watch.md +26 -0
- package/claude/skills/role-orchestrator/scripts/poll.ts +304 -10
- package/claude/skills/role-planner/REQUIREMENT.md +1 -1
- package/claude/skills/role-planner/SKILL.md +4 -2
- package/claude/skills/role-reviewer/REQUIREMENT.md +1 -1
- package/claude/skills/role-reviewer/SKILL.md +3 -1
- package/claude/skills/role-worker/SKILL.md +5 -2
- package/claude/skills/search-craft/SKILL.md +2 -0
- package/claude/skills/seed-sync/SKILL.md +2 -0
- package/claude/skills/session-compact/SKILL.md +2 -0
- package/claude/skills/session-map/SKILL.md +2 -0
- package/claude/skills/session-relay/SKILL.md +2 -0
- package/claude/skills/session-resume/SKILL.md +2 -0
- package/claude/skills/session-worktree/SKILL.md +3 -11
- package/claude/skills/session-worktree/references/install-command.md +20 -0
- package/claude/skills/sketch-design/SKILL.md +2 -0
- package/claude/skills/systematic-debugging/SKILL.md +2 -0
- package/claude/skills/target-check/SKILL.md +2 -0
- package/claude/skills/target-setup/SKILL.md +2 -0
- package/claude/skills/task-board/SKILL.md +2 -0
- package/claude/skills/teach-workspace/SKILL.md +5 -15
- package/claude/skills/teach-workspace/references/lesson-build.md +75 -0
- package/claude/skills/teach-workspace/references/lesson-craft.md +13 -69
- package/claude/skills/teach-workspace/references/revisit.md +22 -0
- package/claude/skills/test-craft/SKILL.md +2 -0
- package/claude/skills/test-first/SKILL.md +2 -0
- package/claude/skills/ui-checklist/SKILL.md +3 -1
- package/claude/skills/ux-audit/SKILL.md +2 -0
- package/claude/skills/ux-measure/SKILL.md +2 -0
- package/claude/skills/ux-walkthrough/SKILL.md +2 -0
- package/claude/skills/video-craft/SKILL.md +2 -0
- package/claude/skills/write-human/SKILL.md +2 -0
- package/claude/skills/youtube-transcripts/SKILL.md +2 -0
- package/docs/agents/canvas.md +24 -8
- package/docs/agents/capture.md +5 -5
- package/docs/agents/commands.md +2 -2
- package/docs/agents/gate.md +3 -1
- package/docs/agents/index.md +5 -4
- package/docs/agents/install-and-sync.md +7 -0
- package/docs/agents/key-changes.md +1 -1
- package/docs/agents/pr-evidence.md +47 -23
- package/docs/agents/pr-local.md +19 -13
- package/docs/agents/pr-preview.md +44 -21
- package/docs/agents/pr-tick.md +72 -0
- package/docs/agents/sandbox.md +30 -6
- package/docs/agents/scripting.md +8 -1
- package/docs/agents/skills-audit.md +2 -1
- package/docs/agents/tasks-plans.md +1 -1
- package/docs/agents/tasks-validate.md +18 -18
- package/docs/agents/tasks.md +1 -1
- package/docs/workflow/operating-model.md +15 -21
- package/docs/workflow/parallel-features.md +24 -3
- package/docs/workflow/skill-map.md +3 -1
- package/docs/workflow/zshrc-aliases.md +1 -1
- package/package.json +1 -1
- package/scripts/core/regen-hero.ts +1 -1
- package/scripts/lib/sandbox-path.sh +6 -162
- package/src/audits/baseline.ts +1 -1
- package/src/audits/run.ts +2 -2
- package/src/canvas/address.ts +12 -3
- package/src/canvas/capture.ts +1 -1
- package/src/canvas/client/inspector/align-grid.tsx +162 -0
- package/src/canvas/client/inspector/color-field.tsx +201 -171
- package/src/canvas/client/inspector/color-inputs.tsx +139 -0
- package/src/canvas/client/inspector/color-picker.tsx +668 -0
- package/src/canvas/client/inspector/color-space.ts +240 -0
- package/src/canvas/client/inspector/field.tsx +66 -3
- package/src/canvas/client/inspector/fields.css +473 -6
- package/src/canvas/client/inspector/size-field.tsx +69 -0
- package/src/canvas/client/inspector/token-select.tsx +109 -0
- package/src/canvas/client/inspector/values.ts +117 -3
- package/src/canvas/client/inspector.tsx +311 -15
- package/src/canvas/client/keys.ts +54 -0
- package/src/canvas/client/selection.tsx +199 -0
- package/src/canvas/client/shell.css +210 -12
- package/src/canvas/client/state.ts +165 -14
- package/src/canvas/client/surface.tsx +341 -39
- package/src/canvas/client/tools.tsx +64 -0
- package/src/canvas/content.ts +51 -2
- package/src/canvas/edit.ts +49 -6
- package/src/canvas/editing.ts +203 -0
- package/src/canvas/global.d.ts +15 -0
- package/src/canvas/server.ts +149 -12
- package/src/canvas/tokens.ts +60 -18
- package/src/census/count.ts +1 -1
- package/src/claude/seeds.ts +3 -3
- package/src/claude/skills-audit.ts +28 -0
- package/src/claude/skills-drift.ts +1 -1
- package/src/claude/skills-families.ts +36 -0
- package/src/claude/skills-list.ts +76 -15
- package/src/claude/skills-reach.ts +1 -1
- package/src/commands/audits.ts +2 -2
- package/src/commands/canvas.ts +180 -3
- package/src/commands/claude.ts +70 -12
- package/src/commands/design.ts +145 -5
- package/src/commands/docs.ts +1 -1
- package/src/commands/feedback.ts +2 -2
- package/src/commands/gate.ts +9 -2
- package/src/commands/gov.ts +3 -3
- package/src/commands/hooks.ts +1 -1
- package/src/commands/indexes.ts +1 -1
- package/src/commands/init.ts +2 -2
- package/src/commands/intake.ts +1 -1
- package/src/commands/migrate.ts +2 -2
- package/src/commands/pr.ts +288 -65
- package/src/commands/records.ts +1 -1
- package/src/commands/repo.ts +1 -1
- package/src/commands/sandbox.ts +64 -15
- package/src/commands/sessions.ts +1 -1
- package/src/commands/slides.ts +1 -1
- package/src/commands/sync.ts +2 -2
- package/src/commands/tasks.ts +21 -4
- package/src/commands/teach.ts +1 -1
- package/src/commands/tooling.ts +1 -1
- package/src/commands/transcripts.ts +2 -2
- package/src/commands/upgrade.ts +1 -1
- package/src/commands/wiki.ts +1 -1
- package/src/commands/worktrees.ts +1 -1
- package/src/comments/scan.ts +1 -1
- package/src/comments/trend.ts +1 -1
- package/src/context/architecture.ts +1 -1
- package/src/context/citations.ts +3 -3
- package/src/context/classify/extract.ts +3 -3
- package/src/context/folders.ts +2 -2
- package/src/context/requirements.ts +1 -1
- package/src/design/project-fonts.ts +392 -0
- package/src/docs/read.ts +1 -1
- package/src/exec.ts +1 -1
- package/src/feedback/github.ts +2 -2
- package/src/gate/measures.ts +48 -10
- package/src/gate/sequencer.ts +56 -4
- package/src/gate/stages.ts +17 -0
- package/src/gate/suite-lock.ts +214 -0
- package/src/{git-files.ts → git/files.ts} +2 -2
- package/src/{git-ignore.ts → git/ignore.ts} +1 -1
- package/src/{worktree.ts → git/worktree.ts} +1 -1
- package/src/gov/citations.ts +1 -1
- package/src/gov/install.ts +1 -1
- package/src/gov/superseded.ts +1 -1
- package/src/gov/test-order.ts +2 -2
- package/src/help.ts +1 -1
- package/src/hooks/post-merge.ts +1 -1
- package/src/indexes/walk.ts +1 -1
- package/src/intake/folder.ts +1 -1
- package/src/labels/audit.ts +1 -1
- package/src/labels/map.ts +1 -1
- package/src/labels/phase.ts +3 -3
- package/src/markdown/files.ts +1 -1
- package/src/migrate/apply.ts +1 -1
- package/src/migrate/record-layout.ts +1 -1
- package/src/migrate/records.ts +11 -11
- package/src/migrate/scratch-evidence.ts +1 -1
- package/src/migrate/surface-roots.ts +6 -6
- package/src/pr/bijection.ts +1 -1
- package/src/pr/evidence.ts +250 -59
- package/src/pr/local.ts +16 -10
- package/src/pr/preview.ts +78 -2
- package/src/pr/tick.ts +56 -0
- package/src/process/harness.ts +1 -1
- package/src/records/backup.ts +2 -2
- package/src/records/ordinal.ts +1 -1
- package/src/records/prune.ts +1 -1
- package/src/records/size.ts +1 -1
- package/src/records/stale-canonical.ts +2 -2
- package/src/records/stale.ts +1 -1
- package/src/records/validate.ts +2 -2
- package/src/{project-root.ts → roots/project.ts} +1 -1
- package/src/{record-root.ts → roots/record.ts} +1 -1
- package/src/sandbox/coverage.ts +11 -12
- package/src/sandbox/equivalence.ts +57 -26
- package/src/sandbox/headless/dispatch.ts +264 -0
- package/src/sandbox/headless/run.ts +653 -0
- package/src/sandbox/headless/watch.ts +145 -0
- package/src/sandbox/hooks.ts +1 -1
- package/src/sandbox/provision.ts +90 -29
- package/src/sandbox/scenario.ts +588 -0
- package/src/sandbox/tree.ts +4 -8
- package/src/secrets/scan.ts +1 -1
- package/src/sessions/claim.ts +1 -1
- package/src/sessions/live.ts +4 -1
- package/src/sessions/resolve.ts +1 -1
- package/src/sessions/transfer/bundle.ts +2 -2
- package/src/slides/decks.ts +1 -1
- package/src/slides/package/fonts.ts +6 -2
- package/src/standards/audit.ts +2 -2
- package/src/standards/read.ts +1 -1
- package/src/sync/check.ts +2 -2
- package/src/sync/engine.ts +3 -3
- package/src/sync/git.ts +1 -1
- package/src/sync/history.ts +1 -1
- package/src/sync/layout.ts +2 -2
- package/src/sync/reverse.ts +1 -1
- package/src/sync/stamp.ts +1 -1
- package/src/sync/target.ts +1 -1
- package/src/sync/workflow.ts +1 -1
- package/src/targets/pulls.ts +2 -2
- package/src/targets/sweep.ts +2 -2
- package/src/{target.ts → targets/validate.ts} +1 -1
- package/src/tasks/answers.ts +1 -1
- package/src/tasks/archive.ts +1 -1
- package/src/tasks/label.ts +1 -1
- package/src/tasks/reach.ts +2 -2
- package/src/tasks/trunk.ts +1 -1
- package/src/tasks/validate.ts +38 -14
- package/src/teach/nav.ts +1 -1
- package/src/teach/workspace.ts +1 -1
- package/src/tooling/inject.ts +2 -2
- package/src/tooling/read.ts +1 -1
- package/src/tooling/scan.ts +1 -1
- package/src/tooling/subfolder.ts +1 -1
- package/src/version/installed.ts +1 -1
- package/src/version/skew.ts +1 -1
- package/src/web/readme-citations.ts +1 -1
- package/src/worktrees/bare-flag.ts +1 -1
- package/src/worktrees/reclaim.ts +2 -2
- package/src/worktrees/remove.ts +1 -1
- package/standards/board.md +3 -3
- package/standards/canvas.md +2 -1
- package/standards/plan.md +13 -7
- package/standards/skill.md +1 -0
- package/standards/wiki.md +12 -3
- package/tooling/base/configs/.github/workflows/pr-local-preview.yml +2 -2
- package/tsconfig.json +1 -1
- package/scripts/lib/sandbox-dispatch.sh +0 -190
- /package/src/{client-commands.ts → gate/client-commands.ts} +0 -0
- /package/src/{git-base.ts → git/base.ts} +0 -0
- /package/src/{git-env.ts → git/env.ts} +0 -0
- /package/src/{legacy-path.ts → roots/legacy.ts} +0 -0
- /package/src/{surface-root.ts → roots/surface.ts} +0 -0
- /package/src/{copy.ts → sync/copy.ts} +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/** @jsxImportSource preact */
|
|
2
|
+
import type { JSX } from 'preact'
|
|
3
|
+
import { useRef, useState } from 'preact/hooks'
|
|
4
|
+
|
|
5
|
+
/** The value each third of an axis writes, start to end as the axis runs. */
|
|
6
|
+
const POSITIONS = ['flex-start', 'center', 'flex-end'] as const
|
|
7
|
+
|
|
8
|
+
const ROWS = ['top', 'middle', 'bottom'] as const
|
|
9
|
+
const COLUMNS = ['left', 'center', 'right'] as const
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Computed values that read as one of the three positions. A container left
|
|
13
|
+
* at its defaults packs from the start, and stretched children do too, so
|
|
14
|
+
* both show the start cell without stating it.
|
|
15
|
+
*/
|
|
16
|
+
const READS: Readonly<Record<string, number>> = {
|
|
17
|
+
normal: 0,
|
|
18
|
+
stretch: 0,
|
|
19
|
+
'flex-start': 0,
|
|
20
|
+
start: 0,
|
|
21
|
+
'self-start': 0,
|
|
22
|
+
left: 0,
|
|
23
|
+
center: 1,
|
|
24
|
+
'flex-end': 2,
|
|
25
|
+
end: 2,
|
|
26
|
+
'self-end': 2,
|
|
27
|
+
right: 2,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Values a container takes without stating a position, which a pick pins. */
|
|
31
|
+
const IMPLIED = new Set(['normal', 'stretch'])
|
|
32
|
+
|
|
33
|
+
export interface Alignment {
|
|
34
|
+
readonly justifyContent: string
|
|
35
|
+
readonly alignItems: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface AlignGridProps {
|
|
39
|
+
/** The container's computed `flex-direction`, which decides the axes. */
|
|
40
|
+
readonly direction: string
|
|
41
|
+
readonly justifyContent: string
|
|
42
|
+
readonly alignItems: string
|
|
43
|
+
readonly isBusy: boolean
|
|
44
|
+
readonly onPick: (alignment: Alignment) => void
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isRow(direction: string): boolean {
|
|
48
|
+
return !direction.startsWith('column')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The cell, as row then column on screen, that a container's alignment puts
|
|
53
|
+
* its children in. A reversed main axis runs from the far edge, so its start
|
|
54
|
+
* is the right or the bottom.
|
|
55
|
+
*/
|
|
56
|
+
function cellOf(
|
|
57
|
+
direction: string,
|
|
58
|
+
justifyContent: string,
|
|
59
|
+
alignItems: string,
|
|
60
|
+
): readonly [number, number] | undefined {
|
|
61
|
+
const main = READS[justifyContent.trim()]
|
|
62
|
+
const cross = READS[alignItems.trim()]
|
|
63
|
+
if (main === undefined || cross === undefined) return undefined
|
|
64
|
+
const along = direction.endsWith('reverse') ? 2 - main : main
|
|
65
|
+
return isRow(direction) ? [cross, along] : [along, cross]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function alignmentOf(
|
|
69
|
+
direction: string,
|
|
70
|
+
row: number,
|
|
71
|
+
column: number,
|
|
72
|
+
): Alignment {
|
|
73
|
+
const [main, cross] = isRow(direction) ? [column, row] : [row, column]
|
|
74
|
+
const along = direction.endsWith('reverse') ? 2 - main : main
|
|
75
|
+
return {
|
|
76
|
+
justifyContent: POSITIONS[along],
|
|
77
|
+
alignItems: POSITIONS[cross],
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const KEY_STEPS: Readonly<Record<string, readonly [number, number]>> = {
|
|
82
|
+
ArrowUp: [-1, 0],
|
|
83
|
+
ArrowDown: [1, 0],
|
|
84
|
+
ArrowLeft: [0, -1],
|
|
85
|
+
ArrowRight: [0, 1],
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Nine cells placing a flex container's children, one radio group. The
|
|
90
|
+
* arrow keys move focus inside the grid and stop at its edges, and a press
|
|
91
|
+
* or Enter or Space picks, since each pick writes the frame twice.
|
|
92
|
+
*/
|
|
93
|
+
export function AlignGrid({
|
|
94
|
+
direction,
|
|
95
|
+
justifyContent,
|
|
96
|
+
alignItems,
|
|
97
|
+
isBusy,
|
|
98
|
+
onPick,
|
|
99
|
+
}: AlignGridProps): JSX.Element {
|
|
100
|
+
const checked = cellOf(direction, justifyContent, alignItems)
|
|
101
|
+
const isImplied =
|
|
102
|
+
IMPLIED.has(justifyContent.trim()) || IMPLIED.has(alignItems.trim())
|
|
103
|
+
const [focus, setFocus] = useState<readonly [number, number]>(
|
|
104
|
+
checked ?? [0, 0],
|
|
105
|
+
)
|
|
106
|
+
const cells = useRef<(HTMLButtonElement | null)[]>([])
|
|
107
|
+
|
|
108
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
109
|
+
const step = KEY_STEPS[event.key]
|
|
110
|
+
if (!step) return
|
|
111
|
+
event.preventDefault()
|
|
112
|
+
const next = [
|
|
113
|
+
Math.min(2, Math.max(0, focus[0] + step[0])),
|
|
114
|
+
Math.min(2, Math.max(0, focus[1] + step[1])),
|
|
115
|
+
] as const
|
|
116
|
+
setFocus(next)
|
|
117
|
+
cells.current[next[0] * 3 + next[1]]?.focus()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div
|
|
122
|
+
class="align-grid"
|
|
123
|
+
role="radiogroup"
|
|
124
|
+
aria-label="Alignment"
|
|
125
|
+
onKeyDown={handleKeyDown}
|
|
126
|
+
>
|
|
127
|
+
{ROWS.map((rowName, row) =>
|
|
128
|
+
COLUMNS.map((columnName, column) => {
|
|
129
|
+
const isChecked = checked?.[0] === row && checked[1] === column
|
|
130
|
+
const isFocusable = focus[0] === row && focus[1] === column
|
|
131
|
+
const name =
|
|
132
|
+
rowName === 'middle' && columnName === 'center'
|
|
133
|
+
? 'center'
|
|
134
|
+
: `${rowName} ${columnName}`
|
|
135
|
+
return (
|
|
136
|
+
<button
|
|
137
|
+
key={name}
|
|
138
|
+
ref={(node) => {
|
|
139
|
+
cells.current[row * 3 + column] = node
|
|
140
|
+
}}
|
|
141
|
+
type="button"
|
|
142
|
+
class="align-cell"
|
|
143
|
+
role="radio"
|
|
144
|
+
aria-label={name}
|
|
145
|
+
aria-checked={isChecked}
|
|
146
|
+
tabIndex={isFocusable ? 0 : -1}
|
|
147
|
+
disabled={isBusy}
|
|
148
|
+
onFocus={() => setFocus([row, column])}
|
|
149
|
+
onClick={() => {
|
|
150
|
+
if (!isChecked || isImplied) {
|
|
151
|
+
onPick(alignmentOf(direction, row, column))
|
|
152
|
+
}
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<span class="align-dot" aria-hidden="true" />
|
|
156
|
+
</button>
|
|
157
|
+
)
|
|
158
|
+
}),
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
/** @jsxImportSource preact */
|
|
2
2
|
import type { JSX } from 'preact'
|
|
3
3
|
import { useLayoutEffect, useRef, useState } from 'preact/hooks'
|
|
4
|
+
import type { ScrubSession } from '@/canvas/client/inspector/field'
|
|
5
|
+
import {
|
|
6
|
+
ColorInputs,
|
|
7
|
+
currentOpacity,
|
|
8
|
+
} from '@/canvas/client/inspector/color-inputs'
|
|
9
|
+
import {
|
|
10
|
+
ColorPicker,
|
|
11
|
+
isPickerHeld,
|
|
12
|
+
releasePicker,
|
|
13
|
+
} from '@/canvas/client/inspector/color-picker'
|
|
4
14
|
import {
|
|
5
15
|
composeColor,
|
|
6
16
|
type ParsedColor,
|
|
7
17
|
readHex,
|
|
8
|
-
readOpacity,
|
|
9
18
|
} from '@/canvas/client/inspector/values'
|
|
10
19
|
|
|
11
20
|
interface ColorFieldProps {
|
|
@@ -15,144 +24,42 @@ interface ColorFieldProps {
|
|
|
15
24
|
readonly color: ParsedColor | undefined
|
|
16
25
|
/** The value as written, shown when `color` is undefined. */
|
|
17
26
|
readonly text: string
|
|
27
|
+
/** The element's own inline value, empty where none stands. */
|
|
28
|
+
readonly inline: string
|
|
18
29
|
/** The color the browser paints, which fills the swatch. */
|
|
19
30
|
readonly painted: string
|
|
20
31
|
/** The color tokens in the order the token sheet sorts them. */
|
|
21
32
|
readonly tokens: readonly string[]
|
|
22
|
-
/** The color a token paints in the frame, read when the
|
|
33
|
+
/** The color a token paints in the frame, read when the list opens. */
|
|
23
34
|
readonly paint: (name: string) => string
|
|
35
|
+
/** The frame, element, and property, so a reload reopens the right picker. */
|
|
36
|
+
readonly owner: string
|
|
24
37
|
readonly isRaw: boolean
|
|
25
38
|
readonly isBusy: boolean
|
|
39
|
+
/** Opens a session previewing into the frame, where the element takes one. */
|
|
40
|
+
readonly preview?: () => ScrubSession
|
|
26
41
|
readonly onCommit: (value: string) => void
|
|
27
42
|
}
|
|
28
43
|
|
|
29
|
-
function shownHex(color: ParsedColor | undefined, text: string): string {
|
|
30
|
-
if (!color) return text
|
|
31
|
-
if (color.kind === 'none') return ''
|
|
32
|
-
return color.kind === 'token' ? color.name : color.hex
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function shownOpacity(color: ParsedColor | undefined): string {
|
|
36
|
-
return color && color.kind !== 'none' ? String(color.opacity) : ''
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function currentOpacity(color: ParsedColor | undefined): number {
|
|
40
|
-
return color && color.kind !== 'none' ? color.opacity : 100
|
|
41
|
-
}
|
|
42
|
-
|
|
43
44
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
45
|
+
* The accessible name of the row control that last wrote a value, so it takes
|
|
46
|
+
* focus back each time the frame reloads. A saved edit remounts the inspector,
|
|
47
|
+
* once for the edit and again when the file watcher reports the write, and each
|
|
48
|
+
* remount replaces the control that held focus and drops it to the page. It
|
|
49
|
+
* holds until focus lands anywhere else.
|
|
46
50
|
*/
|
|
47
|
-
|
|
48
|
-
const hex = readHex(typed)
|
|
49
|
-
if (hex) {
|
|
50
|
-
return composeColor({
|
|
51
|
-
kind: 'hex',
|
|
52
|
-
hex: hex.hex,
|
|
53
|
-
opacity: hex.opacity ?? opacity,
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
if (/^--[\w-]+$/.test(typed)) {
|
|
57
|
-
return composeColor({ kind: 'token', name: typed, opacity })
|
|
58
|
-
}
|
|
59
|
-
return typed
|
|
60
|
-
}
|
|
51
|
+
let refocus: string | undefined
|
|
61
52
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
readonly opacityLabel: string
|
|
65
|
-
readonly color: ParsedColor | undefined
|
|
66
|
-
readonly text: string
|
|
67
|
-
readonly isBusy: boolean
|
|
68
|
-
readonly onCommit: (value: string) => void
|
|
53
|
+
function swatchLabel(label: string): string {
|
|
54
|
+
return `${label} picker`
|
|
69
55
|
}
|
|
70
56
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
* value is what it started at, or an opacity outside 0 to 100.
|
|
74
|
-
*/
|
|
75
|
-
function ColorInputs({
|
|
76
|
-
hexLabel,
|
|
77
|
-
opacityLabel,
|
|
78
|
-
color,
|
|
79
|
-
text,
|
|
80
|
-
isBusy,
|
|
81
|
-
onCommit,
|
|
82
|
-
}: ColorInputsProps): JSX.Element {
|
|
83
|
-
const hex = shownHex(color, text)
|
|
84
|
-
const opacity = shownOpacity(color)
|
|
85
|
-
return (
|
|
86
|
-
<>
|
|
87
|
-
<input
|
|
88
|
-
class={
|
|
89
|
-
color === undefined
|
|
90
|
-
? 'glyph-field-input is-text'
|
|
91
|
-
: color.kind === 'token'
|
|
92
|
-
? 'glyph-field-input is-token'
|
|
93
|
-
: 'glyph-field-input'
|
|
94
|
-
}
|
|
95
|
-
type="text"
|
|
96
|
-
aria-label={hexLabel}
|
|
97
|
-
title={hex}
|
|
98
|
-
defaultValue={hex}
|
|
99
|
-
placeholder="None"
|
|
100
|
-
disabled={isBusy}
|
|
101
|
-
spellcheck={false}
|
|
102
|
-
onChange={(event) => {
|
|
103
|
-
const typed = event.currentTarget.value.trim()
|
|
104
|
-
if (typed === '' || typed === hex) return
|
|
105
|
-
onCommit(fromTyped(typed, currentOpacity(color)))
|
|
106
|
-
}}
|
|
107
|
-
onKeyDown={(event) => {
|
|
108
|
-
if (event.key === 'Escape') event.currentTarget.value = hex
|
|
109
|
-
}}
|
|
110
|
-
/>
|
|
111
|
-
<input
|
|
112
|
-
class="color-opacity"
|
|
113
|
-
type="text"
|
|
114
|
-
inputMode="numeric"
|
|
115
|
-
aria-label={opacityLabel}
|
|
116
|
-
defaultValue={opacity}
|
|
117
|
-
disabled={isBusy || !color || color.kind === 'none'}
|
|
118
|
-
spellcheck={false}
|
|
119
|
-
onChange={(event) => {
|
|
120
|
-
const percent = readOpacity(event.currentTarget.value)
|
|
121
|
-
if (
|
|
122
|
-
percent === undefined ||
|
|
123
|
-
!color ||
|
|
124
|
-
color.kind === 'none' ||
|
|
125
|
-
percent === color.opacity
|
|
126
|
-
) {
|
|
127
|
-
event.currentTarget.value = opacity
|
|
128
|
-
return
|
|
129
|
-
}
|
|
130
|
-
onCommit(composeColor({ ...color, opacity: percent }))
|
|
131
|
-
}}
|
|
132
|
-
onKeyDown={(event) => {
|
|
133
|
-
if (event.key === 'Escape') event.currentTarget.value = opacity
|
|
134
|
-
}}
|
|
135
|
-
/>
|
|
136
|
-
{opacity ? (
|
|
137
|
-
<span class="color-percent" aria-hidden="true">
|
|
138
|
-
%
|
|
139
|
-
</span>
|
|
140
|
-
) : null}
|
|
141
|
-
</>
|
|
142
|
-
)
|
|
57
|
+
function tokensLabel(label: string): string {
|
|
58
|
+
return `${label} tokens`
|
|
143
59
|
}
|
|
144
60
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
* the frame reloads. A saved edit remounts the inspector, once for the edit and
|
|
148
|
-
* again when the file watcher reports the write, and each remount replaces the
|
|
149
|
-
* swatch that held focus and drops it to the page. It holds until focus lands
|
|
150
|
-
* anywhere else.
|
|
151
|
-
*/
|
|
152
|
-
let refocus: string | undefined
|
|
153
|
-
|
|
154
|
-
function swatchLabel(label: string): string {
|
|
155
|
-
return `${label} picker`
|
|
61
|
+
function eyedropperLabel(label: string): string {
|
|
62
|
+
return `${label} eyedropper`
|
|
156
63
|
}
|
|
157
64
|
|
|
158
65
|
if (typeof document !== 'undefined') {
|
|
@@ -161,28 +68,87 @@ if (typeof document !== 'undefined') {
|
|
|
161
68
|
const target = event.target
|
|
162
69
|
const name =
|
|
163
70
|
target instanceof Element ? target.getAttribute('aria-label') : null
|
|
164
|
-
if (name !==
|
|
71
|
+
if (name !== refocus) refocus = undefined
|
|
165
72
|
})
|
|
166
73
|
}
|
|
167
74
|
|
|
75
|
+
/** The screen sampler, where the browser ships one. */
|
|
76
|
+
function eyeDropper(): (new () => EyeDropperSampler) | undefined {
|
|
77
|
+
return typeof window === 'undefined' ? undefined : window.EyeDropper
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
type Open = 'picker' | 'tokens' | undefined
|
|
81
|
+
|
|
82
|
+
const GAP = 8
|
|
83
|
+
|
|
168
84
|
/**
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
85
|
+
* The left edge of the scrolling panel holding the row, so a popover clears
|
|
86
|
+
* the panel's padding rather than overlapping it. The row's own edge stands in
|
|
87
|
+
* where no ancestor scrolls.
|
|
88
|
+
*/
|
|
89
|
+
function panelLeft(row: HTMLElement): number {
|
|
90
|
+
for (let node = row.parentElement; node; node = node.parentElement) {
|
|
91
|
+
const overflow = getComputedStyle(node).overflowY
|
|
92
|
+
if (overflow === 'auto' || overflow === 'scroll') {
|
|
93
|
+
return node.getBoundingClientRect().left
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return row.getBoundingClientRect().left
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Places a popover beside the row, to the left of the panel, and holds it
|
|
101
|
+
* inside the window. The panel scrolls and clips, so the popover sits in the
|
|
102
|
+
* top layer where the browser has one and at fixed coordinates either way.
|
|
103
|
+
*/
|
|
104
|
+
function place(popover: HTMLElement, row: HTMLElement): void {
|
|
105
|
+
if (
|
|
106
|
+
typeof popover.showPopover === 'function' &&
|
|
107
|
+
!popover.matches(':popover-open')
|
|
108
|
+
) {
|
|
109
|
+
popover.showPopover()
|
|
110
|
+
}
|
|
111
|
+
const box = popover.getBoundingClientRect()
|
|
112
|
+
const anchor = row.getBoundingClientRect()
|
|
113
|
+
const width = document.documentElement.clientWidth || window.innerWidth
|
|
114
|
+
const height = document.documentElement.clientHeight || window.innerHeight
|
|
115
|
+
const before = panelLeft(row) - GAP - box.width
|
|
116
|
+
const left =
|
|
117
|
+
before >= GAP
|
|
118
|
+
? before
|
|
119
|
+
: Math.max(GAP, Math.min(anchor.right + GAP, width - box.width - GAP))
|
|
120
|
+
const top = Math.max(GAP, Math.min(anchor.top, height - box.height - GAP))
|
|
121
|
+
popover.style.left = `${left}px`
|
|
122
|
+
popover.style.top = `${top}px`
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* One color property: a swatch opening the picker, the hex, the opacity, a
|
|
127
|
+
* tokens icon opening the project's color tokens, and an eyedropper where the
|
|
128
|
+
* browser has one. A token keeps following the theme and a literal does not,
|
|
129
|
+
* so the tokens sit one press from the row.
|
|
172
130
|
*/
|
|
173
131
|
export function ColorField({
|
|
174
132
|
label,
|
|
175
133
|
color,
|
|
176
134
|
text,
|
|
135
|
+
inline,
|
|
177
136
|
painted,
|
|
178
137
|
tokens,
|
|
179
138
|
paint,
|
|
139
|
+
owner,
|
|
180
140
|
isRaw,
|
|
181
141
|
isBusy,
|
|
142
|
+
preview,
|
|
182
143
|
onCommit,
|
|
183
144
|
}: ColorFieldProps): JSX.Element {
|
|
184
|
-
const [
|
|
145
|
+
const [open, setOpen] = useState<Open>(() =>
|
|
146
|
+
isPickerHeld(owner) ? 'picker' : undefined,
|
|
147
|
+
)
|
|
148
|
+
const root = useRef<HTMLDivElement>(null)
|
|
149
|
+
const row = useRef<HTMLDivElement>(null)
|
|
185
150
|
const swatch = useRef<HTMLButtonElement>(null)
|
|
151
|
+
const tokenButton = useRef<HTMLButtonElement>(null)
|
|
186
152
|
const list = useRef<HTMLUListElement>(null)
|
|
187
153
|
const dialog = useRef<HTMLDivElement>(null)
|
|
188
154
|
const picked = color?.kind === 'token' ? tokens.indexOf(color.name) : -1
|
|
@@ -191,31 +157,32 @@ export function ColorField({
|
|
|
191
157
|
|
|
192
158
|
/*
|
|
193
159
|
* Focus moves in the commit rather than after paint, so a key typed straight
|
|
194
|
-
* after the one that opened the
|
|
160
|
+
* after the one that opened the list reaches it, not the button.
|
|
195
161
|
*/
|
|
196
162
|
useLayoutEffect(() => {
|
|
197
|
-
if (!
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
null
|
|
202
|
-
target?.focus()
|
|
203
|
-
}, [isOpen])
|
|
163
|
+
if (!open || !dialog.current || !row.current) return
|
|
164
|
+
place(dialog.current, row.current)
|
|
165
|
+
if (open === 'tokens') (list.current ?? dialog.current).focus()
|
|
166
|
+
}, [open])
|
|
204
167
|
|
|
205
168
|
useLayoutEffect(() => {
|
|
206
|
-
if (refocus ===
|
|
169
|
+
if (refocus === undefined) return
|
|
170
|
+
root.current
|
|
171
|
+
?.querySelector<HTMLElement>(`[aria-label="${refocus}"]`)
|
|
172
|
+
?.focus()
|
|
207
173
|
}, [label])
|
|
208
174
|
|
|
209
175
|
const close = () => {
|
|
210
|
-
|
|
211
|
-
|
|
176
|
+
const trigger = open === 'tokens' ? tokenButton : swatch
|
|
177
|
+
releasePicker()
|
|
178
|
+
setOpen(undefined)
|
|
179
|
+
trigger.current?.focus()
|
|
212
180
|
}
|
|
213
181
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
onCommit(value)
|
|
182
|
+
const toggle = (next: Exclude<Open, undefined>) => {
|
|
183
|
+
releasePicker()
|
|
184
|
+
setActive(Math.max(0, picked))
|
|
185
|
+
setOpen(open === next ? undefined : next)
|
|
219
186
|
}
|
|
220
187
|
|
|
221
188
|
const pick = (name: string) => {
|
|
@@ -223,24 +190,55 @@ export function ColorField({
|
|
|
223
190
|
close()
|
|
224
191
|
return
|
|
225
192
|
}
|
|
226
|
-
|
|
193
|
+
close()
|
|
194
|
+
refocus = tokensLabel(label)
|
|
195
|
+
onCommit(
|
|
227
196
|
composeColor({ kind: 'token', name, opacity: currentOpacity(color) }),
|
|
228
197
|
)
|
|
229
198
|
}
|
|
230
199
|
|
|
200
|
+
const Sampler = eyeDropper()
|
|
201
|
+
const sample = async (sampler: new () => EyeDropperSampler) => {
|
|
202
|
+
let picked: string
|
|
203
|
+
try {
|
|
204
|
+
picked = (await new sampler().open()).sRGBHex
|
|
205
|
+
} catch {
|
|
206
|
+
/* The operator dismissed the sampler, which writes nothing. */
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
const hex = readHex(picked)
|
|
210
|
+
if (!hex) return
|
|
211
|
+
refocus = eyedropperLabel(label)
|
|
212
|
+
onCommit(
|
|
213
|
+
composeColor({
|
|
214
|
+
kind: 'hex',
|
|
215
|
+
hex: hex.hex,
|
|
216
|
+
opacity: currentOpacity(color),
|
|
217
|
+
}),
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
231
221
|
const id = `color-${label.replace(/\W+/g, '-')}`
|
|
232
222
|
|
|
233
223
|
return (
|
|
234
224
|
<div
|
|
225
|
+
ref={root}
|
|
235
226
|
class="color-field is-wide"
|
|
236
227
|
onFocusOut={(event) => {
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
228
|
+
const field = event.currentTarget
|
|
229
|
+
/*
|
|
230
|
+
* Read once focus has settled. A reload removes the field while it
|
|
231
|
+
* holds focus, and a picker held across that reload must stay open.
|
|
232
|
+
*/
|
|
233
|
+
setTimeout(() => {
|
|
234
|
+
if (!field.isConnected) return
|
|
235
|
+
if (field.contains(document.activeElement)) return
|
|
236
|
+
releasePicker()
|
|
237
|
+
setOpen(undefined)
|
|
238
|
+
}, 0)
|
|
241
239
|
}}
|
|
242
240
|
>
|
|
243
|
-
<div class="fill-row">
|
|
241
|
+
<div ref={row} class="fill-row">
|
|
244
242
|
<div class="glyph-field">
|
|
245
243
|
<button
|
|
246
244
|
ref={swatch}
|
|
@@ -248,13 +246,10 @@ export function ColorField({
|
|
|
248
246
|
type="button"
|
|
249
247
|
aria-label={swatchLabel(label)}
|
|
250
248
|
aria-haspopup="dialog"
|
|
251
|
-
aria-expanded={
|
|
249
|
+
aria-expanded={open === 'picker'}
|
|
252
250
|
title={label}
|
|
253
251
|
disabled={isBusy}
|
|
254
|
-
onClick={() =>
|
|
255
|
-
setActive(Math.max(0, picked))
|
|
256
|
-
setOpen(!isOpen)
|
|
257
|
-
}}
|
|
252
|
+
onClick={() => toggle('picker')}
|
|
258
253
|
>
|
|
259
254
|
<span
|
|
260
255
|
class="swatch-paint"
|
|
@@ -270,6 +265,38 @@ export function ColorField({
|
|
|
270
265
|
onCommit={onCommit}
|
|
271
266
|
/>
|
|
272
267
|
</div>
|
|
268
|
+
<button
|
|
269
|
+
ref={tokenButton}
|
|
270
|
+
class="color-icon"
|
|
271
|
+
type="button"
|
|
272
|
+
aria-label={tokensLabel(label)}
|
|
273
|
+
aria-haspopup="dialog"
|
|
274
|
+
aria-expanded={open === 'tokens'}
|
|
275
|
+
title="Pick a color token"
|
|
276
|
+
disabled={isBusy}
|
|
277
|
+
onClick={() => toggle('tokens')}
|
|
278
|
+
>
|
|
279
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
280
|
+
<circle cx="5" cy="5" r="2.25" />
|
|
281
|
+
<circle cx="11" cy="5" r="2.25" />
|
|
282
|
+
<circle cx="5" cy="11" r="2.25" />
|
|
283
|
+
<circle cx="11" cy="11" r="2.25" />
|
|
284
|
+
</svg>
|
|
285
|
+
</button>
|
|
286
|
+
{Sampler ? (
|
|
287
|
+
<button
|
|
288
|
+
class="color-icon"
|
|
289
|
+
type="button"
|
|
290
|
+
aria-label={eyedropperLabel(label)}
|
|
291
|
+
title="Pick a color from the screen"
|
|
292
|
+
disabled={isBusy}
|
|
293
|
+
onClick={() => void sample(Sampler)}
|
|
294
|
+
>
|
|
295
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
296
|
+
<path d="M10.5 2.5a1.4 1.4 0 0 1 2 0l1 1a1.4 1.4 0 0 1 0 2L12 7l.5.5-1 1-4-4 1-1L9 4zM7.5 5.5l3 3-5 5H2.5v-3z" />
|
|
297
|
+
</svg>
|
|
298
|
+
</button>
|
|
299
|
+
) : null}
|
|
273
300
|
{isRaw ? (
|
|
274
301
|
<span
|
|
275
302
|
class="raw"
|
|
@@ -279,12 +306,17 @@ export function ColorField({
|
|
|
279
306
|
</span>
|
|
280
307
|
) : null}
|
|
281
308
|
</div>
|
|
282
|
-
{
|
|
309
|
+
{open ? (
|
|
283
310
|
<div
|
|
284
311
|
ref={dialog}
|
|
285
|
-
class=
|
|
312
|
+
class={
|
|
313
|
+
open === 'picker' ? 'color-popover' : 'color-popover is-tokens'
|
|
314
|
+
}
|
|
315
|
+
popover="manual"
|
|
286
316
|
role="dialog"
|
|
287
|
-
aria-label={
|
|
317
|
+
aria-label={
|
|
318
|
+
open === 'picker' ? `${label} color` : `${label} token list`
|
|
319
|
+
}
|
|
288
320
|
tabIndex={-1}
|
|
289
321
|
onKeyDown={(event) => {
|
|
290
322
|
if (event.key !== 'Escape') return
|
|
@@ -296,7 +328,18 @@ export function ColorField({
|
|
|
296
328
|
close()
|
|
297
329
|
}}
|
|
298
330
|
>
|
|
299
|
-
{
|
|
331
|
+
{open === 'picker' ? (
|
|
332
|
+
<ColorPicker
|
|
333
|
+
owner={owner}
|
|
334
|
+
color={color}
|
|
335
|
+
text={text}
|
|
336
|
+
inline={inline}
|
|
337
|
+
painted={painted}
|
|
338
|
+
begin={preview}
|
|
339
|
+
isBusy={isBusy}
|
|
340
|
+
onCommit={onCommit}
|
|
341
|
+
/>
|
|
342
|
+
) : tokens.length > 0 ? (
|
|
300
343
|
<ul
|
|
301
344
|
ref={list}
|
|
302
345
|
class="token-list"
|
|
@@ -348,19 +391,6 @@ export function ColorField({
|
|
|
348
391
|
No color tokens resolve. Add them to the token sheet
|
|
349
392
|
</p>
|
|
350
393
|
)}
|
|
351
|
-
<div class="glyph-field">
|
|
352
|
-
<span class="glyph" aria-hidden="true">
|
|
353
|
-
#
|
|
354
|
-
</span>
|
|
355
|
-
<ColorInputs
|
|
356
|
-
hexLabel="hex"
|
|
357
|
-
opacityLabel="opacity"
|
|
358
|
-
color={color}
|
|
359
|
-
text={text}
|
|
360
|
-
isBusy={isBusy}
|
|
361
|
-
onCommit={commitFromPicker}
|
|
362
|
-
/>
|
|
363
|
-
</div>
|
|
364
394
|
</div>
|
|
365
395
|
) : null}
|
|
366
396
|
</div>
|