@erclx/canon 5.25.0 → 5.27.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 +4 -3
- 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 +14 -1
- 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/REQUIREMENT.md +6 -2
- package/claude/skills/context-fold/SKILL.md +9 -21
- package/claude/skills/context-fold/references/anchor-sweep.md +2 -2
- 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/REQUIREMENT.md +1 -1
- package/claude/skills/design-taste/SKILL.md +6 -2
- package/claude/skills/design-taste/references/adopted.md +40 -0
- package/claude/skills/design-taste/references/kinds.md +1 -1
- package/claude/skills/design-taste/references/systems.md +1 -7
- package/claude/skills/design-taste/references/tells.md +2 -24
- package/claude/skills/docs-sync/REQUIREMENT.md +2 -1
- package/claude/skills/docs-sync/SKILL.md +2 -0
- package/claude/skills/document-health/REQUIREMENT.md +2 -0
- package/claude/skills/document-health/SKILL.md +23 -0
- package/claude/skills/document-health/references/axes.md +12 -0
- package/claude/skills/draft-and-pick/SKILL.md +3 -1
- 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 +3 -1
- package/claude/skills/draft-ready/SKILL.md +2 -0
- package/claude/skills/draft-screencast/SKILL.md +11 -7
- package/claude/skills/draft-slides/REQUIREMENT.md +17 -12
- package/claude/skills/draft-slides/SKILL.md +33 -20
- 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-issue/references/issue.md +10 -0
- package/claude/skills/git-pr/SKILL.md +2 -0
- package/claude/skills/git-pr/references/evidence.md +14 -6
- package/claude/skills/git-pr/references/labels.md +12 -0
- package/claude/skills/git-ship/SKILL.md +2 -0
- package/claude/skills/git-split/SKILL.md +21 -8
- 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/markdown-propose/references/format.md +9 -0
- package/claude/skills/memory-capture/SKILL.md +2 -0
- package/claude/skills/memory-review/SKILL.md +2 -0
- package/claude/skills/memory-review/references/later-phases.md +7 -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/SKILL.md +3 -7
- package/claude/skills/review-address/references/post-review-findings.md +16 -0
- 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 +2 -2
- 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/SKILL.md +3 -1
- package/claude/skills/review-ui/references/post.md +4 -2
- package/claude/skills/role-orchestrator/SKILL.md +5 -14
- package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +8 -0
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +13 -0
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-inbound.md +23 -0
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +5 -33
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +1 -21
- 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-watch.md +26 -0
- package/claude/skills/role-planner/SKILL.md +2 -0
- package/claude/skills/role-reviewer/REQUIREMENT.md +1 -1
- package/claude/skills/role-reviewer/SKILL.md +2 -0
- package/claude/skills/role-worker/SKILL.md +2 -0
- package/claude/skills/search-craft/REQUIREMENT.md +1 -1
- package/claude/skills/search-craft/SKILL.md +5 -1
- package/claude/skills/search-craft/references/adopted.md +30 -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/target-setup/references/detect.md +8 -0
- package/claude/skills/target-setup/references/indexes.md +13 -0
- package/claude/skills/target-setup/references/verify.md +8 -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 +4 -0
- package/claude/skills/test-first/references/adopted.md +36 -0
- package/claude/skills/ui-checklist/SKILL.md +2 -0
- 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 +3 -1
- package/claude/skills/write-human/references/machine-tells.md +13 -0
- package/claude/skills/youtube-transcripts/SKILL.md +2 -0
- package/docs/agents/canvas.md +24 -6
- package/docs/agents/capture.md +6 -6
- package/docs/agents/commands.md +5 -5
- package/docs/agents/index.md +5 -5
- package/docs/agents/install-and-sync.md +7 -0
- package/docs/agents/key-changes.md +1 -1
- package/docs/agents/pr-evidence.md +38 -15
- package/docs/agents/pr-local.md +19 -13
- package/docs/agents/pr-preview.md +44 -21
- package/docs/agents/records-stale.md +22 -9
- package/docs/agents/records.md +1 -1
- package/docs/agents/sandbox.md +29 -5
- package/docs/agents/scripting.md +8 -1
- package/docs/agents/skills-audit.md +11 -3
- package/docs/agents/slides.md +15 -3
- package/docs/workflow/ai-workflow.md +1 -1
- package/docs/workflow/index.md +1 -0
- package/docs/workflow/operating-model.md +5 -6
- package/docs/workflow/parallel-features.md +23 -2
- package/docs/workflow/skill-map-on-request.md +42 -0
- package/docs/workflow/skill-map.md +3 -34
- 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-git.sh +1 -9
- package/scripts/lib/sandbox-path.sh +6 -162
- package/scripts/sandbox-hook.sh +99 -0
- package/src/audits/run.ts +1 -1
- package/src/canvas/address.ts +12 -3
- package/src/canvas/capture.ts +1 -9
- package/src/canvas/client/inspector/align-grid.tsx +162 -0
- package/src/canvas/client/inspector/color-field.tsx +398 -0
- 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 +5 -1
- package/src/canvas/client/inspector/fields.css +547 -17
- package/src/canvas/client/inspector/size-field.tsx +69 -0
- package/src/canvas/client/inspector/values.ts +166 -1
- package/src/canvas/client/inspector.tsx +236 -73
- package/src/canvas/client/selection.tsx +197 -0
- package/src/canvas/client/shell.css +104 -12
- package/src/canvas/client/state.ts +69 -9
- package/src/canvas/client/surface.tsx +147 -22
- package/src/canvas/content.ts +50 -1
- package/src/canvas/edit.ts +37 -4
- 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 +59 -17
- package/src/capture/render.ts +45 -7
- package/src/capture/sources.ts +43 -0
- package/src/census/count.ts +2 -2
- package/src/claude/cases/workflow.ts +5 -0
- package/src/claude/seeds.ts +2 -2
- package/src/claude/skills-audit.ts +96 -14
- 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/commands/audits.ts +2 -2
- package/src/commands/canvas.ts +179 -2
- package/src/commands/claude.ts +94 -11
- package/src/commands/design.ts +142 -2
- package/src/commands/feedback.ts +3 -3
- package/src/commands/gov.ts +1 -1
- package/src/commands/hooks.ts +1 -1
- package/src/commands/indexes.ts +1 -1
- package/src/commands/init.ts +1 -1
- package/src/commands/intake.ts +1 -1
- package/src/commands/migrate.ts +1 -1
- package/src/commands/pr.ts +105 -58
- package/src/commands/records.ts +89 -7
- package/src/commands/repo.ts +1 -1
- package/src/commands/sandbox.ts +103 -11
- package/src/commands/sessions.ts +1 -1
- package/src/commands/slides.ts +31 -77
- package/src/commands/sync.ts +1 -1
- package/src/commands/tasks.ts +2 -2
- package/src/commands/teach.ts +1 -1
- package/src/commands/transcripts.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/audit.ts +1 -1
- package/src/context/citations.ts +1 -1
- package/src/context/classify/extract.ts +2 -2
- package/src/design/project-fonts.ts +392 -0
- package/src/design/tokens.ts +2 -18
- package/src/docs/read.ts +1 -1
- package/src/{github.ts → feedback/github.ts} +2 -2
- package/src/{client-commands.ts → gate/client-commands.ts} +1 -1
- package/src/gate/measures.ts +53 -13
- package/src/gate/sequencer.ts +1 -1
- package/src/gate/stages.ts +9 -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 +2 -2
- package/src/gov/install.ts +1 -1
- package/src/gov/payload.ts +1 -1
- package/src/gov/superseded.ts +3 -3
- 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/labels/audit.ts +1 -1
- package/src/markdown/ceiling.ts +1 -1
- package/src/markdown/files.ts +1 -1
- package/src/migrate/apply.ts +1 -1
- package/src/pr/bijection.ts +1 -1
- package/src/pr/evidence.ts +96 -43
- package/src/pr/local.ts +16 -10
- package/src/pr/preview.ts +78 -2
- package/src/record-root.ts +1 -0
- package/src/records/backup.ts +2 -1
- package/src/records/push-guard.ts +1 -1
- package/src/records/stale-canonical.ts +146 -0
- package/src/records/stale.ts +1 -1
- 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 +128 -0
- package/src/sandbox/provision.ts +607 -0
- package/src/sandbox/tree.ts +99 -4
- package/src/secrets/marker.ts +2 -2
- package/src/secrets/scan.ts +2 -2
- package/src/sessions/claim.ts +1 -1
- package/src/sessions/resolve.ts +1 -1
- package/src/sessions/transfer/bundle.ts +1 -1
- package/src/shipped/references.ts +2 -2
- package/src/slides/convert/export.ts +2 -16
- package/src/slides/decks.ts +110 -0
- 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 +2 -2
- package/src/sync/git.ts +1 -1
- package/src/sync/history.ts +1 -1
- package/src/sync/reverse.ts +1 -1
- package/src/sync/seeds-report.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/tasks/answers.ts +1 -1
- package/src/tasks/archive.ts +1 -1
- package/src/tasks/reach.ts +1 -1
- package/src/tasks/trunk.ts +1 -1
- package/src/teach/nav.ts +1 -1
- package/src/tooling/inject.ts +2 -2
- package/src/tooling/subfolder.ts +1 -1
- package/src/web/readme-citations.ts +2 -2
- package/src/worktrees/bare-flag.ts +1 -1
- package/src/worktrees/reclaim.ts +2 -2
- package/src/worktrees/remove.ts +1 -1
- package/standards/architecture.md +1 -0
- package/standards/canvas.md +3 -2
- package/standards/requirements.md +2 -0
- package/standards/skill-practice.md +2 -1
- package/standards/skill.md +2 -0
- package/standards/wiki.md +12 -3
- package/tooling/base/configs/.github/workflows/pr-local-preview.yml +2 -2
- package/scripts/lib/sandbox-dispatch.sh +0 -190
- package/scripts/manage-sandbox.sh +0 -472
- package/src/slides/layouts.ts +0 -478
- package/src/slides/parse.ts +0 -84
- package/src/slides/render.ts +0 -110
- package/src/slides/styles.ts +0 -96
- /package/claude/skills/write-human/references/{source-material.md → adopted.md} +0 -0
- /package/src/{commands/feedback-format.ts → feedback/body.ts} +0 -0
- /package/src/{github-format.ts → feedback/github-format.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/{frontmatter.ts → markdown/frontmatter.ts} +0 -0
- /package/src/{binary.ts → sweep/binary.ts} +0 -0
- /package/src/{exempt-marker.ts → sweep/exempt-marker.ts} +0 -0
- /package/src/{copy.ts → sync/copy.ts} +0 -0
- /package/src/{target.ts → targets/validate.ts} +0 -0
- /package/src/{paths.ts → tasks/paths.ts} +0 -0
- /package/src/{seed-marker.ts → tooling/seed-marker.ts} +0 -0
|
@@ -5,7 +5,25 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/** Properties whose bare numbers are pixel lengths. */
|
|
8
|
-
const LENGTHS = new Set([
|
|
8
|
+
const LENGTHS = new Set([
|
|
9
|
+
'width',
|
|
10
|
+
'height',
|
|
11
|
+
'padding',
|
|
12
|
+
'gap',
|
|
13
|
+
'font-size',
|
|
14
|
+
'border-radius',
|
|
15
|
+
'border-top-left-radius',
|
|
16
|
+
'border-top-right-radius',
|
|
17
|
+
'border-bottom-right-radius',
|
|
18
|
+
'border-bottom-left-radius',
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
/** Properties a size mode writes, each mode by the name the field shows. */
|
|
22
|
+
const SIZES = new Set(['width', 'height'])
|
|
23
|
+
const SIZE_MODES: Readonly<Record<string, string>> = {
|
|
24
|
+
Fill: '100%',
|
|
25
|
+
Fit: 'fit-content',
|
|
26
|
+
}
|
|
9
27
|
|
|
10
28
|
const PX = /^(-?\d*\.?\d+)px$/
|
|
11
29
|
|
|
@@ -28,6 +46,14 @@ export function displayValue(property: string, raw: string): string {
|
|
|
28
46
|
const value = raw.trim()
|
|
29
47
|
if (property === 'gap' && (value === '' || value === 'normal')) return ''
|
|
30
48
|
if (property === 'background-color' && isNoFill(value)) return ''
|
|
49
|
+
if (property === 'opacity' && value !== '')
|
|
50
|
+
return `${Math.round(Number(value) * 100)}%`
|
|
51
|
+
if (SIZES.has(property)) {
|
|
52
|
+
const mode = Object.keys(SIZE_MODES).find(
|
|
53
|
+
(name) => SIZE_MODES[name] === value,
|
|
54
|
+
)
|
|
55
|
+
if (mode) return mode
|
|
56
|
+
}
|
|
31
57
|
if (value === 'auto') return 'Auto'
|
|
32
58
|
if (value === 'normal') return 'Normal'
|
|
33
59
|
return value.split(/\s+/).map(formatPx).join(' ')
|
|
@@ -36,6 +62,7 @@ export function displayValue(property: string, raw: string): string {
|
|
|
36
62
|
export function toCssValue(property: string, typed: string): string {
|
|
37
63
|
const value = typed.trim()
|
|
38
64
|
if (value === 'Auto' || value === 'Normal') return value.toLowerCase()
|
|
65
|
+
if (SIZES.has(property) && SIZE_MODES[value]) return SIZE_MODES[value]
|
|
39
66
|
if (!LENGTHS.has(property)) return value
|
|
40
67
|
return value
|
|
41
68
|
.split(/\s+/)
|
|
@@ -43,6 +70,21 @@ export function toCssValue(property: string, typed: string): string {
|
|
|
43
70
|
.join(' ')
|
|
44
71
|
}
|
|
45
72
|
|
|
73
|
+
/** A typed opacity percent as the fraction CSS takes, refused outside 0 to 100. */
|
|
74
|
+
export function opacityToCss(typed: string): string | undefined {
|
|
75
|
+
const percent = readOpacity(typed)
|
|
76
|
+
return percent === undefined ? undefined : String(percent / 100)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The radius all four corners share, read in the order the longhands run,
|
|
81
|
+
* or undefined where any corner differs and the panel shows each one.
|
|
82
|
+
*/
|
|
83
|
+
export function sharedRadius(corners: readonly string[]): string | undefined {
|
|
84
|
+
const [first] = corners
|
|
85
|
+
return corners.every((corner) => corner === first) ? first : undefined
|
|
86
|
+
}
|
|
87
|
+
|
|
46
88
|
/**
|
|
47
89
|
* Holds a scrubbed number inside what the property accepts. The writer takes
|
|
48
90
|
* any declaration that does not break the rule, so a negative length or a
|
|
@@ -58,3 +100,126 @@ export function clampScrub(property: string, value: number): number {
|
|
|
58
100
|
export function scrubStep(isCoarse: boolean): number {
|
|
59
101
|
return isCoarse ? 10 : 1
|
|
60
102
|
}
|
|
103
|
+
|
|
104
|
+
/** A color the picker can write: a token or a hex, each with an opacity. */
|
|
105
|
+
export type WrittenColor =
|
|
106
|
+
| {
|
|
107
|
+
readonly kind: 'token'
|
|
108
|
+
/** The custom property name as the token sheet gives it. */
|
|
109
|
+
readonly name: string
|
|
110
|
+
readonly opacity: number
|
|
111
|
+
}
|
|
112
|
+
| { readonly kind: 'hex'; readonly hex: string; readonly opacity: number }
|
|
113
|
+
|
|
114
|
+
export type ParsedColor = WrittenColor | { readonly kind: 'none' }
|
|
115
|
+
|
|
116
|
+
const HEX = /^#?([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i
|
|
117
|
+
const RGB =
|
|
118
|
+
/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:\s*[,/]\s*([\d.]+%?))?\s*\)$/i
|
|
119
|
+
/** What a browser computes `color-mix()` in srgb to, channels 0 to 1. */
|
|
120
|
+
const SRGB =
|
|
121
|
+
/^color\(\s*srgb\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)(?:\s*\/\s*([\d.]+%?))?\s*\)$/i
|
|
122
|
+
const VAR = /^var\(\s*(--[\w-]+)\s*\)$/i
|
|
123
|
+
const MIX =
|
|
124
|
+
/^color-mix\(\s*in\s+srgb\s*,\s*var\(\s*(--[\w-]+)\s*\)\s+([\d.]+)%\s*,\s*transparent\s*\)$/i
|
|
125
|
+
|
|
126
|
+
function channel(value: number): string {
|
|
127
|
+
return Math.round(Math.min(255, Math.max(0, value)))
|
|
128
|
+
.toString(16)
|
|
129
|
+
.padStart(2, '0')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* An alpha as a percent. A value past 1 clamps to full rather than reading as
|
|
134
|
+
* a 0 to 255 scale, since CSS clamps it the same way when it paints.
|
|
135
|
+
*/
|
|
136
|
+
function alphaPercent(alpha: string | undefined): number {
|
|
137
|
+
if (alpha === undefined) return 100
|
|
138
|
+
const fraction = alpha.endsWith('%')
|
|
139
|
+
? Number(alpha.slice(0, -1)) / 100
|
|
140
|
+
: Number(alpha)
|
|
141
|
+
return Math.round(Math.min(1, Math.max(0, fraction)) * 100)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** A typed hex in any of its lengths, normalized to six digits. */
|
|
145
|
+
export function readHex(
|
|
146
|
+
typed: string,
|
|
147
|
+
): { readonly hex: string; readonly opacity?: number } | undefined {
|
|
148
|
+
const match = typed.trim().match(HEX)
|
|
149
|
+
if (!match) return undefined
|
|
150
|
+
const digits = match[1].toLowerCase()
|
|
151
|
+
const full =
|
|
152
|
+
digits.length <= 4
|
|
153
|
+
? [...digits].map((digit) => digit + digit).join('')
|
|
154
|
+
: digits
|
|
155
|
+
const hex = full.slice(0, 6)
|
|
156
|
+
if (full.length === 6) return { hex }
|
|
157
|
+
return {
|
|
158
|
+
hex,
|
|
159
|
+
opacity: Math.round((parseInt(full.slice(6), 16) / 255) * 100),
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** A typed opacity percent, refused outside 0 to 100. */
|
|
164
|
+
export function readOpacity(typed: string): number | undefined {
|
|
165
|
+
const value = typed.trim().replace(/%$/, '')
|
|
166
|
+
if (!/^-?\d*\.?\d+$/.test(value)) return undefined
|
|
167
|
+
const percent = Number(value)
|
|
168
|
+
return percent >= 0 && percent <= 100 ? Math.round(percent) : undefined
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Reads a computed or inline color into what the picker shows. A form it does
|
|
173
|
+
* not know, such as a named color, comes back undefined.
|
|
174
|
+
*/
|
|
175
|
+
export function parseColor(value: string): ParsedColor | undefined {
|
|
176
|
+
const text = value.trim()
|
|
177
|
+
if (isNoFill(text)) return { kind: 'none' }
|
|
178
|
+
const token = text.match(VAR)
|
|
179
|
+
if (token) return { kind: 'token', name: token[1], opacity: 100 }
|
|
180
|
+
const mix = text.match(MIX)
|
|
181
|
+
if (mix) {
|
|
182
|
+
return {
|
|
183
|
+
kind: 'token',
|
|
184
|
+
name: mix[1],
|
|
185
|
+
opacity: Math.round(Math.min(100, Number(mix[2]))),
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const hex = text.startsWith('#') ? readHex(text) : undefined
|
|
189
|
+
if (hex) return painted(hex.hex, hex.opacity ?? 100)
|
|
190
|
+
const rgb = text.match(RGB)
|
|
191
|
+
if (rgb) {
|
|
192
|
+
return painted(
|
|
193
|
+
[rgb[1], rgb[2], rgb[3]].map((part) => channel(Number(part))).join(''),
|
|
194
|
+
alphaPercent(rgb[4]),
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
const srgb = text.match(SRGB)
|
|
198
|
+
if (!srgb) return undefined
|
|
199
|
+
return painted(
|
|
200
|
+
[srgb[1], srgb[2], srgb[3]]
|
|
201
|
+
.map((part) => channel(Number(part) * 255))
|
|
202
|
+
.join(''),
|
|
203
|
+
alphaPercent(srgb[4]),
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** A color at zero opacity paints nothing, whichever form wrote it. */
|
|
208
|
+
function painted(hex: string, opacity: number): ParsedColor {
|
|
209
|
+
return opacity === 0 ? { kind: 'none' } : { kind: 'hex', hex, opacity }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* The value a pick writes. A token below full opacity mixes with transparent
|
|
214
|
+
* rather than resolving to a hex, so it keeps following the theme.
|
|
215
|
+
*/
|
|
216
|
+
export function composeColor(color: WrittenColor): string {
|
|
217
|
+
if (color.kind === 'token') {
|
|
218
|
+
return color.opacity >= 100
|
|
219
|
+
? `var(${color.name})`
|
|
220
|
+
: `color-mix(in srgb, var(${color.name}) ${color.opacity}%, transparent)`
|
|
221
|
+
}
|
|
222
|
+
return color.opacity >= 100
|
|
223
|
+
? `#${color.hex}`
|
|
224
|
+
: `#${color.hex}${channel((color.opacity / 100) * 255)}`
|
|
225
|
+
}
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
/** @jsxImportSource preact */
|
|
2
2
|
import type { JSX } from 'preact'
|
|
3
|
+
import { useState } from 'preact/hooks'
|
|
3
4
|
import { addressOf, elementAt, excerpt, isRawText } from '@/canvas/address'
|
|
5
|
+
import { AlignGrid } from '@/canvas/client/inspector/align-grid'
|
|
6
|
+
import { ColorField } from '@/canvas/client/inspector/color-field'
|
|
4
7
|
import {
|
|
5
8
|
Field,
|
|
6
9
|
ReadOnlyField,
|
|
7
10
|
type Scrub,
|
|
8
11
|
} from '@/canvas/client/inspector/field'
|
|
9
12
|
import { Section } from '@/canvas/client/inspector/section'
|
|
13
|
+
import { SizeField } from '@/canvas/client/inspector/size-field'
|
|
10
14
|
import {
|
|
11
15
|
clampScrub,
|
|
12
16
|
displayValue,
|
|
17
|
+
opacityToCss,
|
|
18
|
+
parseColor,
|
|
19
|
+
sharedRadius,
|
|
13
20
|
toCssValue,
|
|
14
21
|
} from '@/canvas/client/inspector/values'
|
|
15
22
|
import {
|
|
@@ -64,26 +71,56 @@ const SIZE_FIELDS: readonly StyleField[] = [
|
|
|
64
71
|
{ label: 'height', glyph: 'H', property: 'height' },
|
|
65
72
|
]
|
|
66
73
|
|
|
74
|
+
const DIRECTION_FIELD: StyleField = {
|
|
75
|
+
label: 'direction',
|
|
76
|
+
glyph: 'Dir',
|
|
77
|
+
property: 'flex-direction',
|
|
78
|
+
}
|
|
79
|
+
|
|
67
80
|
const FLEX_FIELDS: readonly StyleField[] = [
|
|
68
|
-
{ label: 'direction', glyph: 'Dir', property: 'flex-direction' },
|
|
69
81
|
{ label: 'gap', glyph: 'Gap', property: 'gap', placeholder: '0' },
|
|
70
|
-
{ label: 'padding', glyph: 'Pad', property: 'padding', isWide: true },
|
|
71
82
|
]
|
|
72
83
|
|
|
84
|
+
/** Padding applies to any box, so it sits in Layout rather than Flex. */
|
|
85
|
+
const PADDING_FIELD: StyleField = {
|
|
86
|
+
label: 'padding',
|
|
87
|
+
glyph: 'Pad',
|
|
88
|
+
property: 'padding',
|
|
89
|
+
isWide: true,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The corners in the order the radius shorthand lists them. */
|
|
93
|
+
const CORNER_FIELDS: readonly StyleField[] = [
|
|
94
|
+
{ label: 'top left radius', glyph: 'TL', property: 'border-top-left-radius' },
|
|
95
|
+
{
|
|
96
|
+
label: 'top right radius',
|
|
97
|
+
glyph: 'TR',
|
|
98
|
+
property: 'border-top-right-radius',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
label: 'bottom right radius',
|
|
102
|
+
glyph: 'BR',
|
|
103
|
+
property: 'border-bottom-right-radius',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
label: 'bottom left radius',
|
|
107
|
+
glyph: 'BL',
|
|
108
|
+
property: 'border-bottom-left-radius',
|
|
109
|
+
},
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
const FLEX_DISPLAYS = new Set(['flex', 'inline-flex'])
|
|
113
|
+
const GRID_DISPLAYS = new Set(['grid', 'inline-grid'])
|
|
114
|
+
|
|
73
115
|
const TYPE_FIELDS: readonly StyleField[] = [
|
|
74
116
|
{ label: 'size', glyph: 'Size', property: 'font-size' },
|
|
75
117
|
{ label: 'weight', glyph: 'Wt', property: 'font-weight' },
|
|
76
118
|
]
|
|
77
119
|
|
|
78
|
-
const FILL_FIELDS
|
|
79
|
-
{ label: 'color',
|
|
80
|
-
{
|
|
81
|
-
|
|
82
|
-
glyph: 'Bg',
|
|
83
|
-
property: 'background-color',
|
|
84
|
-
placeholder: 'None',
|
|
85
|
-
},
|
|
86
|
-
]
|
|
120
|
+
const FILL_FIELDS = [
|
|
121
|
+
{ label: 'color', property: 'color' },
|
|
122
|
+
{ label: 'background', property: 'background-color' },
|
|
123
|
+
] as const
|
|
87
124
|
|
|
88
125
|
function inlineValue(element: Element, property: string): string {
|
|
89
126
|
return 'style' in element
|
|
@@ -127,8 +164,10 @@ function isRawInline(element: Element, property: string): boolean {
|
|
|
127
164
|
* an edit starts from what the operator sees.
|
|
128
165
|
*/
|
|
129
166
|
function currentValue(element: Element, property: string): string {
|
|
130
|
-
|
|
131
|
-
|
|
167
|
+
return inlineValue(element, property) || computedValue(element, property)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function computedValue(element: Element, property: string): string {
|
|
132
171
|
return (
|
|
133
172
|
element.ownerDocument.defaultView
|
|
134
173
|
?.getComputedStyle(element)
|
|
@@ -136,6 +175,19 @@ function currentValue(element: Element, property: string): string {
|
|
|
136
175
|
)
|
|
137
176
|
}
|
|
138
177
|
|
|
178
|
+
/**
|
|
179
|
+
* The color a token paints in the frame, read off a probe rather than the
|
|
180
|
+
* token's text, since a token's value may itself be a `var()` or a mix.
|
|
181
|
+
*/
|
|
182
|
+
function tokenPaint(doc: Document, name: string): string {
|
|
183
|
+
const probe = doc.createElement('span')
|
|
184
|
+
probe.style.setProperty('color', `var(${name})`)
|
|
185
|
+
;(doc.body ?? doc.documentElement).append(probe)
|
|
186
|
+
const painted = computedValue(probe, 'color')
|
|
187
|
+
probe.remove()
|
|
188
|
+
return painted
|
|
189
|
+
}
|
|
190
|
+
|
|
139
191
|
function holdsTextAlone(element: Element): boolean {
|
|
140
192
|
const tag = element.tagName.toLowerCase()
|
|
141
193
|
return element.children.length === 0 && !VOID.has(tag) && !isRawText(tag)
|
|
@@ -146,46 +198,6 @@ function elementName(element: Element): string {
|
|
|
146
198
|
return [tag, ...element.classList].join('.')
|
|
147
199
|
}
|
|
148
200
|
|
|
149
|
-
interface TokenPickerProps {
|
|
150
|
-
readonly label: string
|
|
151
|
-
readonly initial: string
|
|
152
|
-
readonly isBusy: boolean
|
|
153
|
-
readonly onCommit: (value: string) => void
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function TokenPicker({
|
|
157
|
-
label,
|
|
158
|
-
initial,
|
|
159
|
-
isBusy,
|
|
160
|
-
onCommit,
|
|
161
|
-
}: TokenPickerProps): JSX.Element | null {
|
|
162
|
-
const colors =
|
|
163
|
-
tokens.value?.groups?.find((group) => group.kind === 'color')?.tokens ?? []
|
|
164
|
-
if (colors.length === 0) return null
|
|
165
|
-
return (
|
|
166
|
-
<select
|
|
167
|
-
class="token-picker"
|
|
168
|
-
aria-label={`${label} token`}
|
|
169
|
-
title="Pick a project token"
|
|
170
|
-
value={
|
|
171
|
-
colors.some((token) => initial === `var(${token.name})`) ? initial : ''
|
|
172
|
-
}
|
|
173
|
-
disabled={isBusy}
|
|
174
|
-
onChange={(event) => {
|
|
175
|
-
const { value } = event.currentTarget
|
|
176
|
-
if (value !== '' && value !== initial) onCommit(value)
|
|
177
|
-
}}
|
|
178
|
-
>
|
|
179
|
-
<option value="">Token</option>
|
|
180
|
-
{colors.map((token) => (
|
|
181
|
-
<option key={token.name} value={`var(${token.name})`}>
|
|
182
|
-
{token.name}
|
|
183
|
-
</option>
|
|
184
|
-
))}
|
|
185
|
-
</select>
|
|
186
|
-
)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
201
|
function ElementFields({
|
|
190
202
|
node,
|
|
191
203
|
doc,
|
|
@@ -203,9 +215,38 @@ function ElementFields({
|
|
|
203
215
|
const commit = (property: string) => (value: string) => {
|
|
204
216
|
if (address) void editElement(frameRef, key, address, property, value)
|
|
205
217
|
}
|
|
218
|
+
/*
|
|
219
|
+
* The writer takes one property an edit, so a control setting two posts
|
|
220
|
+
* them in turn and stops at the first refusal. Each carries the hash the
|
|
221
|
+
* last answered with in its own address, since a frame reloading between
|
|
222
|
+
* the two clears the stored one.
|
|
223
|
+
*/
|
|
224
|
+
const commitAll = async (
|
|
225
|
+
changes: readonly (readonly [string, string])[],
|
|
226
|
+
): Promise<void> => {
|
|
227
|
+
let current = address
|
|
228
|
+
for (const [property, value] of changes) {
|
|
229
|
+
if (!current) return
|
|
230
|
+
const written = await editElement(frameRef, key, current, property, value)
|
|
231
|
+
if (!written) return
|
|
232
|
+
if (written.hash) current = { ...current, hash: written.hash }
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const corners = CORNER_FIELDS.map((field) =>
|
|
236
|
+
currentValue(node, field.property).trim(),
|
|
237
|
+
)
|
|
238
|
+
const radius = sharedRadius(corners)
|
|
239
|
+
const [isPerCorner, setPerCorner] = useState(radius === undefined)
|
|
240
|
+
const display = computedValue(node, 'display').trim()
|
|
241
|
+
const isFlex = FLEX_DISPLAYS.has(display)
|
|
242
|
+
const isWrapped = computedValue(node, 'flex-wrap').trim() === 'wrap'
|
|
206
243
|
const rect = node.getBoundingClientRect()
|
|
207
244
|
const font = currentValue(node, 'font-family')
|
|
208
245
|
const text = node.textContent ?? ''
|
|
246
|
+
const colorTokens =
|
|
247
|
+
tokens.value?.groups
|
|
248
|
+
?.find((group) => group.kind === 'color')
|
|
249
|
+
?.tokens.map((token) => token.name) ?? []
|
|
209
250
|
|
|
210
251
|
/**
|
|
211
252
|
* Previews into the frame's own inline style and writes through the same
|
|
@@ -261,9 +302,125 @@ function ElementFields({
|
|
|
261
302
|
glyph="Y"
|
|
262
303
|
value={String(Math.round(rect.top))}
|
|
263
304
|
/>
|
|
264
|
-
{SIZE_FIELDS.map(
|
|
305
|
+
{SIZE_FIELDS.map((field) => (
|
|
306
|
+
<SizeField
|
|
307
|
+
key={field.property}
|
|
308
|
+
label={field.label}
|
|
309
|
+
glyph={field.glyph}
|
|
310
|
+
initial={displayValue(
|
|
311
|
+
field.property,
|
|
312
|
+
currentValue(node, field.property),
|
|
313
|
+
)}
|
|
314
|
+
isBusy={isBusy}
|
|
315
|
+
onCommit={(typed) =>
|
|
316
|
+
commit(field.property)(toCssValue(field.property, typed))
|
|
317
|
+
}
|
|
318
|
+
scrub={'style' in node ? scrubOf(field.property) : undefined}
|
|
319
|
+
/>
|
|
320
|
+
))}
|
|
321
|
+
{styleField(PADDING_FIELD)}
|
|
322
|
+
</Section>
|
|
323
|
+
<Section
|
|
324
|
+
title="Flex"
|
|
325
|
+
action={
|
|
326
|
+
isFlex || GRID_DISPLAYS.has(display) ? null : (
|
|
327
|
+
<button
|
|
328
|
+
type="button"
|
|
329
|
+
class="section-icon"
|
|
330
|
+
aria-label="Add flex layout"
|
|
331
|
+
title="Add flex layout"
|
|
332
|
+
disabled={isBusy}
|
|
333
|
+
onClick={() => commit('display')('flex')}
|
|
334
|
+
>
|
|
335
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
336
|
+
<path d="M8 3.5v9M3.5 8h9" />
|
|
337
|
+
</svg>
|
|
338
|
+
</button>
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
>
|
|
342
|
+
{isFlex ? (
|
|
343
|
+
<>
|
|
344
|
+
<div class="flex-layout is-wide">
|
|
345
|
+
<AlignGrid
|
|
346
|
+
direction={computedValue(node, 'flex-direction').trim()}
|
|
347
|
+
justifyContent={computedValue(node, 'justify-content')}
|
|
348
|
+
alignItems={computedValue(node, 'align-items')}
|
|
349
|
+
isBusy={isBusy}
|
|
350
|
+
onPick={(alignment) =>
|
|
351
|
+
void commitAll([
|
|
352
|
+
['justify-content', alignment.justifyContent],
|
|
353
|
+
['align-items', alignment.alignItems],
|
|
354
|
+
])
|
|
355
|
+
}
|
|
356
|
+
/>
|
|
357
|
+
<div class="flex-layout-side">
|
|
358
|
+
{styleField(DIRECTION_FIELD)}
|
|
359
|
+
<button
|
|
360
|
+
type="button"
|
|
361
|
+
class="section-icon"
|
|
362
|
+
aria-label="Wrap"
|
|
363
|
+
title="Wrap"
|
|
364
|
+
aria-pressed={isWrapped}
|
|
365
|
+
disabled={isBusy}
|
|
366
|
+
onClick={() =>
|
|
367
|
+
commit('flex-wrap')(isWrapped ? 'nowrap' : 'wrap')
|
|
368
|
+
}
|
|
369
|
+
>
|
|
370
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
371
|
+
<path d="M2.5 4.5h9a2.5 2.5 0 0 1 0 5h-6M7.5 7.5l-2 2 2 2M2.5 12.5h1" />
|
|
372
|
+
</svg>
|
|
373
|
+
</button>
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
{FLEX_FIELDS.map(styleField)}
|
|
377
|
+
</>
|
|
378
|
+
) : null}
|
|
379
|
+
</Section>
|
|
380
|
+
<Section
|
|
381
|
+
title="Appearance"
|
|
382
|
+
action={
|
|
383
|
+
<button
|
|
384
|
+
type="button"
|
|
385
|
+
class="section-icon"
|
|
386
|
+
aria-label="Per-corner radius"
|
|
387
|
+
title="Per-corner radius"
|
|
388
|
+
aria-pressed={isPerCorner}
|
|
389
|
+
onClick={() => setPerCorner(!isPerCorner)}
|
|
390
|
+
>
|
|
391
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
392
|
+
<path d="M3 6.5V5a2 2 0 0 1 2-2h1.5M9.5 3H11a2 2 0 0 1 2 2v1.5M13 9.5V11a2 2 0 0 1-2 2H9.5M6.5 13H5a2 2 0 0 1-2-2V9.5" />
|
|
393
|
+
</svg>
|
|
394
|
+
</button>
|
|
395
|
+
}
|
|
396
|
+
>
|
|
397
|
+
<Field
|
|
398
|
+
label="opacity"
|
|
399
|
+
glyph="Op"
|
|
400
|
+
initial={displayValue('opacity', computedValue(node, 'opacity'))}
|
|
401
|
+
isBusy={isBusy}
|
|
402
|
+
isWide={isPerCorner}
|
|
403
|
+
onCommit={(typed) => {
|
|
404
|
+
const value = opacityToCss(typed)
|
|
405
|
+
if (value !== undefined) commit('opacity')(value)
|
|
406
|
+
}}
|
|
407
|
+
/>
|
|
408
|
+
{isPerCorner ? (
|
|
409
|
+
CORNER_FIELDS.map(styleField)
|
|
410
|
+
) : (
|
|
411
|
+
<Field
|
|
412
|
+
label="radius"
|
|
413
|
+
glyph="R"
|
|
414
|
+
initial={displayValue('border-radius', radius ?? '')}
|
|
415
|
+
isBusy={isBusy}
|
|
416
|
+
placeholder="0"
|
|
417
|
+
onCommit={(typed) =>
|
|
418
|
+
commit('border-radius')(toCssValue('border-radius', typed))
|
|
419
|
+
}
|
|
420
|
+
scrub={'style' in node ? scrubOf('border-radius') : undefined}
|
|
421
|
+
/>
|
|
422
|
+
)}
|
|
265
423
|
</Section>
|
|
266
|
-
<Section title="Flex">{FLEX_FIELDS.map(styleField)}</Section>
|
|
267
424
|
<Section title="Typography">
|
|
268
425
|
{TYPE_FIELDS.map(styleField)}
|
|
269
426
|
{font ? (
|
|
@@ -272,25 +429,31 @@ function ElementFields({
|
|
|
272
429
|
</Section>
|
|
273
430
|
<Section title="Fill">
|
|
274
431
|
{FILL_FIELDS.map((field) => {
|
|
275
|
-
const
|
|
432
|
+
const painted = computedValue(node, field.property)
|
|
433
|
+
const inline = inlineValue(node, field.property)
|
|
434
|
+
/*
|
|
435
|
+
* An inline value the picker cannot read stays as written. Reading
|
|
436
|
+
* the computed color in its place would show a token-following
|
|
437
|
+
* value as a literal, and an opacity edit would freeze it.
|
|
438
|
+
*/
|
|
276
439
|
return (
|
|
277
|
-
<
|
|
278
|
-
{
|
|
279
|
-
{
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
440
|
+
<ColorField
|
|
441
|
+
key={field.property}
|
|
442
|
+
label={field.label}
|
|
443
|
+
color={parseColor(inline || painted)}
|
|
444
|
+
text={inline || painted}
|
|
445
|
+
inline={inline}
|
|
446
|
+
painted={painted}
|
|
447
|
+
owner={`${key}:${address?.index ?? -1}:${field.property}`}
|
|
448
|
+
tokens={colorTokens}
|
|
449
|
+
paint={(name) => tokenPaint(doc, name)}
|
|
450
|
+
isRaw={isRawInline(node, field.property)}
|
|
451
|
+
isBusy={isBusy}
|
|
452
|
+
preview={
|
|
453
|
+
'style' in node ? scrubOf(field.property).begin : undefined
|
|
454
|
+
}
|
|
455
|
+
onCommit={commit(field.property)}
|
|
456
|
+
/>
|
|
294
457
|
)
|
|
295
458
|
})}
|
|
296
459
|
</Section>
|