@beehexa/vueflow-tokens 2611.5.5 → 2611.8.1

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 CHANGED
@@ -24,45 +24,112 @@ host/dimension changes. The factory uses VueFlow 1's real `fitViewOnInit` option
24
24
 
25
25
  ## Public contracts
26
26
 
27
- - `createVueFlowTheme({ dark?, locale? })`: scoped source variables, real VueFlow
28
- viewport settings, background and minimap bindings.
29
- - `NodeType`, `NODE_DIMENSIONS`, `GRID_SPACING`, `TASK_DATA_LAYOUT`: source-backed
30
- fixed geometry shared by renderers and graph layout. Do not recreate these tables.
31
- - `getFlowNodeStyles(type, { hovered?, selected?, selfDependency? })`: typed custom
32
- node body/content/icon/label/connector styles, without decorative motion or gradients.
33
- - `getNodeColorPalette(category)`: CSS-backed paired category roles, live across
34
- theme switches. Categories are task, dependency, transformation, validation,
35
- pushStep, decision, loop and grouped; they identify kinds, never execution status.
36
- - `getNodeColor(type)`, `getEdgeColorPalette(category)`, `styleTargetEdge(edge, nodes)`:
37
- source-backed directed edge identity; target stroke and marker remain paired.
27
+ - `createVueFlowTheme({ dark?, locale?, nodeTone? })`: scoped source variables, real VueFlow
28
+ viewport settings, background and minimap bindings. `nodeTone(node)` picks each MiniMap
29
+ node's tone (`blue`, VueFlow's default-node colour, when omitted).
30
+ - `FLOW_TONES` / `FlowTone`: the generic colour tones `blue`, `red`, `green`, `crimson`,
31
+ `yellow`, `teal`, `magenta` and `purple`. Each tone reads one existing foundation
32
+ `flow.category.*` role set (foundation names unchanged); the package gives a tone no
33
+ meaning, and the consumer owns which of its concepts takes which tone. Tones identify
34
+ kinds, never execution status.
35
+ - `getFlowTonePalette(tone)`: CSS-backed paired tone roles, live across theme switches.
36
+ - `getFlowToneStyles(tone, part?)`: custom content role binding; `foreground` (default)
37
+ sets tone text, `border` sets border colour and `background` sets the tone surface.
38
+ - `getFlowNodeStyles({ tone, width, height, shape? }, { hovered?, selected?, dashed? })`:
39
+ typed custom node body/content/icon/label/logo styles over consumer-owned geometry,
40
+ without decorative motion or gradients. `shape: 'circle'` rounds the body fully.
41
+ - `styleTargetEdge(edge, nodes, toneOf?)`: directed edge identity from the target node's
42
+ tone (`blue` when omitted); target stroke and marker remain paired.
38
43
  - `FLOW_EDGE`, `FLOW_ICON`: numeric source values at VueFlow/icon API boundaries.
39
- - `getFlowCategoryStyles(type, part?)`: custom semantic content role binding;
40
- `foreground` (default) sets category text, `border` sets border colour and
41
- `background` sets the category surface.
42
- - `getFlowIconStyles(type, { size?, color?, disabled? })`: category-border glyphs
44
+ - `getFlowIconStyles(toneOrRole, { size?, color?, disabled? })`: tone-border glyphs
43
45
  or separate `status-success|warning|error|info|loading` / `control-expand|collapse|help`
44
- icon roles. Edge icon keys use `edge-dependency|edge-transformation`.
45
- Sizes accept `small|medium|large` or a numeric API size; explicit colours must
46
- already be source-resolved semantic roles.
47
- - `getFlowEdgeStyles(category, suppliedStyle?)`: SVG width, non-scaling stroke,
48
- no-fill and static relation dashes. The supplied directed target stroke wins
49
- over category fallback, preserving stroke/arrow identity.
50
- - Public option, category, dimension, node-state, style and theme interfaces are emitted
46
+ icon roles. Sizes accept `small|medium|large` or a numeric API size; explicit colours
47
+ must already be source-resolved semantic roles.
48
+ - `getFlowEdgeStyles(tone, suppliedStyle?)`: SVG width, non-scaling stroke and no fill.
49
+ A supplied directed target stroke wins over the tone fallback, and a supplied static
50
+ dash is kept; the consumer owns what a dash means.
51
+ - Public option, tone, node-look, node-state, style and theme interfaces are emitted
51
52
  in `dist/index.d.ts`; CSS has a typed `./css` export.
52
53
 
53
54
  Use public source typography/content markers and graph presentation factories in
54
- custom semantic content. Keep category labels and icons, selected outline, separate
55
- keyboard focus and readable error messages. Do not append hexadecimal alpha suffixes
56
- to CSS variables. Do not animate idle graph nodes, rotate category icons, add
57
- glass/gradients, or override core controls in each consumer.
55
+ custom content. Keep kind labels and icons, selected outline, separate keyboard focus
56
+ and readable error messages. Do not append hexadecimal alpha suffixes to CSS
57
+ variables. Do not animate idle graph nodes, rotate kind icons, add glass/gradients,
58
+ or override core controls in each consumer.
58
59
  Preserve graph IDs, stored coordinates and relationship direction on theme changes.
59
60
  Exports must capture the current graph surface/font rather than force white/system fonts.
60
61
 
61
- Ordinary nodes, MiniMap cards and Controls use `radius.100` (8 px). Controls
62
- round exposed corners without clipping focus, and MiniMap fills clip.
63
- Connector logos retain nested `radius.075` (6 px). Decision/loop nodes and graph
64
- handles keep their circular geometry; `FLOW_EDGE.radius` remains edge-path geometry.
65
- Consumer-owned inspector cards and controls/toolbars use `--bh-radius-100`; dialogs/drawers use `radius.150` (12 px).
62
+ Native `.bh-workflow-canvas` consumers may opt into the `WorkflowEdge.tone` values
63
+ `blue`, `red` and `green`. The companion binds `--wf-edge-tone-<tone>` to the existing
64
+ foundation border roles `--bh-flow-category-task-border`, `-dependency-border` and
65
+ `-transformation-border` (foundation names unchanged), with solid, short-dash (`2 4`) and
66
+ dash-dot (`9 3 2 3`) cues through `--wf-edge-tone-<tone>-dash`. Tone is categorical,
67
+ independent of failure; native stroke/arrowhead, interaction states and jump swatches
68
+ share it. The consumer maps its own relation kinds onto these tones. PNG export reuses
69
+ the native exporter's DOM/stylesheet serialization, with no tone-specific path.
70
+ Omitting tone retains existing native styling. Do not add consumer edge renderers.
71
+
72
+ Possible, context and toned-failure edges keep tone ink and dash in every state;
73
+ their cues are native shapes, never new dashes or opacity. `certainty: 'possible'`
74
+ draws a hollow source-end diamond (BPMN conditional flow). `context: true` draws an
75
+ open arrowhead and the thinner resting stroke `--wf-edge-context-width`, a package-owned
76
+ variable bound to the foundation `border.width.default`. A toned failure keeps its
77
+ tone dash and adds the Lucide `CircleX` mark in `color.icon.danger`
78
+ (`--wf-edge-failure-icon`) beside the label; an untoned failure keeps the failure dash.
79
+ Pass the localized group name as `familyLabel` for `{family} jump to {target}` names,
80
+ and localize the whole name/numbering through the viewer's `texts.jumps` (`WorkflowJumpTexts`).
81
+
82
+ `NodeLook.sizing: 'content'` opts ordinary native cards into intrinsic width:
83
+ minimum `diagram.card.width`, maximum twice that source role. The native theme owns
84
+ only that width rule; this companion owns the roles
85
+ `--wf-node-content-min-width` / `--wf-node-content-max-width` and the presentation
86
+ recipe (title/kind wrap, growing head, logos bounded without hiding any), with no
87
+ declaration duplicated in the native theme. Other cards retain their existing
88
+ fixed sizing; stadium, diamond and ellipse cards ignore the opt-in.
89
+ Native measurement/router and endpoint spreading are unchanged. Light/dark,
90
+ long title/many-logo, interaction and exported-PNG acceptance remains manual.
91
+
92
+ Native group frames (`WorkflowNode.group`, the built-in `wf-group` node) take the
93
+ package-owned `--wf-group-*` variables, each bound to an existing foundation role:
94
+ border `color.border`, border width `border.width.default`, radius `radius.card`, fill
95
+ `color.surface.subtle`, nested fill `color.background.neutral`, padding `space.stack`
96
+ and header gap `space.150`; high contrast draws the border in text ink. No flow-specific
97
+ foundation token backs them. The native layout reads padding and header gap from the
98
+ canvas, so both stay plain lengths.
99
+ Diamond and ellipse titles and kind captions wrap at word boundaries and are never
100
+ truncated (no `overflow-wrap: anywhere`, no ellipsis); only a single word wider than the
101
+ content box breaks. Other cards may still break a long unbroken title anywhere. Frame
102
+ look, toggle and exported-PNG acceptance remain manual.
103
+
104
+ The native Shift + drag selection box takes `--wf-selection-*` from the same selection
105
+ recipe as `.bh-vue-flow .vue-flow__selection`: transparent (no fill, so swept cards stay visible), a
106
+ dashed `border.width.selected` border in `color.border.focus`, in both themes. Its look remains
107
+ manual acceptance.
108
+
109
+ The native find box (`WorkflowFind`, the viewer's `/` search) takes `--wf-find-*` and the
110
+ shared `--wf-list-*` roles: an overlay (`color.surface.overlay`, `elevation.shadow.overlay`,
111
+ `radius.overlay`, no border) with a `color.border` divider under the field. Field and empty
112
+ state `font.body.default` (placeholder `color.text.subtlest`, empty state `color.text.subtle`);
113
+ rows `font.label.default`, `space.075` / `space.item.x` padding, `size.control.default` minimum
114
+ height (`size.target.touch` on coarse pointers and below 768px), `radius.item`; the kind label
115
+ `font.body.small` in `color.text.subtle`; an unnamed node's id `font.code.default`. Names wrap at
116
+ word boundaries, never truncated. Row states: hover `color.surface.overlay.hover`; the
117
+ keyboard-active row `color.surface.overlay.selected` plus the `border.width.selected` /
118
+ `color.border.selected` start bar; that row hovered `color.surface.overlay.selected.hover`.
119
+ Focus is the field ring (`--wf-focus-outline`) around the box. The canvas previews the pointed
120
+ row's node with its hover state and outer ring, without changing selection, trace or viewport.
121
+ The `--wf-list-*` roles also paint the native jump list and dock menu rows. Look and contrast
122
+ in both themes remain manual acceptance.
123
+
124
+ Ordinary nodes, MiniMap cards and outer Controls/dock frames use `radius.100`
125
+ (8px). Individual compact control Buttons and menu items retain `radius.050` (4px);
126
+ floating zoom-menu surfaces use `radius.overlay` (8px), independently of those items.
127
+ MiniMap fills clip; Controls retain exposed focus rings. Native legend popovers
128
+ keep `radius.100`, while the native keyboard-shortcuts Dialog uses `radius.150`
129
+ (12px), independently of its control Buttons. Node logos retain nested
130
+ `radius.075` (6px). Circular nodes and graph handles keep their circular
131
+ geometry; `FLOW_EDGE.radius` remains edge-path geometry. Consumer-owned inspector
132
+ Cards use `radius.100`; dialogs/drawers use `radius.150`.
66
133
 
67
134
  Bind `data-bh-high-contrast="true"` on the actual `bh-vue-flow` host when the user's
68
135
  high-contrast setting is enabled. Native node/edge/path, graph buttons and custom
@@ -73,30 +140,30 @@ same host for the existing user accessibility settings (1.125em node text;
73
140
  1.6 line-height / 0.02em letter-spacing). These are opt-in adaptations, not new
74
141
  default typography. `bh-flow-icon[data-bh-loading="true"]` animates only an
75
142
  actual loading glyph and respects reduced motion.
76
- `bh-flow-category-mark[data-bh-category="<FlowCategory>"]` is a labelled 6px
77
- circular relationship marker in the category's border role, never a status dot.
143
+ `bh-flow-tone-mark[data-bh-tone="<FlowTone>"]` is a labelled 6px circular
144
+ relationship marker in the tone's border role (`blue` when unset), never a status dot.
78
145
 
79
- For a native category Tag, bind the cached immutable result of
80
- `getFlowCategoryBadgeStyles(type)` to its public `style` prop and omit its
81
- `color` prop. This pairs category foreground, background and border; a frame/edge
82
- stroke from `getNodeColor(type)` is not a filled badge background. Both themes'
146
+ For a native kind Tag, bind the cached immutable result of
147
+ `getFlowToneBadgeStyles(tone)` to its public `style` prop and omit its `color` prop.
148
+ This pairs tone foreground, background and border; a frame/edge stroke from
149
+ `getFlowTonePalette(tone).border` is not a filled badge background. Both themes'
83
150
  small-text pairs are checked at WCAG AA 4.5:1 and demonstrated with real native
84
- Tags in the Ant Design Vue family. Category labels are not execution status.
151
+ Tags in the Ant Design Vue family. Tones are not execution status.
85
152
 
86
- Resting handles use the neutral icon role, not keyboard-focus color. TaskFlow-style
87
- graph fullscreen uses an isolated local stacking context at `z-index.modal` (400), allowing
153
+ Resting handles use the neutral icon role, not keyboard-focus color. Graph
154
+ fullscreen uses an isolated local stacking context at `z-index.modal` (400), allowing
88
155
  global inspectors above it. Child chrome may use private z10/z15 within that context,
89
- not global1001/1002. This is not an editor takeover at `z-index.fullscreen` (450).
156
+ not global1001/1002. Editor takeovers use the same `z-index.modal` layer.
90
157
 
91
158
  ## Build and validation
92
159
 
93
160
  The shared `build-runtime-packages.mjs` compiles strict NodeNext source and checks generated
94
161
  output. Actual packed-consumer checks exercise exports/CSS with strict TypeScript and
95
162
  `skipLibCheck` at the upstream vendor-declaration boundary. Source version `0.0.0`
96
- stays private; only the authorized runner stamps real package/internal dependency versions.
163
+ stays private; only the authorized runner stamps real versions for this package and its internal requirements.
97
164
 
98
165
  Full authored guidance lives in `src/content/tools/vueflow.mdx` and ships through
99
166
  `@beehexa/design-rules`. Verify real light/dark and English/Vietnamese surfaces,
100
- contrast, all fixed dimensions, long labels, selection/focus/error, target-coloured
167
+ contrast, consumer-owned dimensions, long labels, selection/focus/error, target-coloured
101
168
  markers, controls/minimap, zoom/snap/fit, expanded/cyclic graphs, fullscreen/ESC and PNG
102
169
  export. Typed configuration is not visual or accessibility proof.
package/dist/index.d.ts CHANGED
@@ -1,61 +1,25 @@
1
1
  import type { CSSProperties } from 'vue';
2
2
  import { type FlowProps, type Node, type Edge, type Styles } from '@vue-flow/core';
3
+ /**
4
+ * Generic colour tones. Each tone reads one existing foundation `flow.category.<key>` role set;
5
+ * the foundation keys keep their names and the package gives a tone no domain meaning. The
6
+ * consumer decides which of its own concepts takes which tone.
7
+ */
8
+ export declare const FLOW_TONES: readonly ["blue", "red", "green", "crimson", "yellow", "teal", "magenta", "purple"];
9
+ export type FlowTone = typeof FLOW_TONES[number];
3
10
  export interface VueFlowThemeOptions {
4
11
  dark?: boolean;
5
12
  locale?: 'en' | 'vi';
13
+ /** Tone of a MiniMap node; `blue` when omitted. */
14
+ nodeTone?: (node: Node) => FlowTone;
6
15
  }
7
- export type FlowCategory = 'task' | 'dependency' | 'transformation' | 'validation' | 'pushStep' | 'decision' | 'loop' | 'grouped';
8
- export type FlowEdgeCategory = 'dependency' | 'transformation' | 'dataFlow' | 'conditional';
9
- export interface FlowNodePalette {
16
+ export interface FlowTonePalette {
10
17
  primary: string;
11
18
  text: string;
12
19
  background: string;
13
20
  border: string;
14
21
  hover: string;
15
- connector: {
16
- bg: string;
17
- border: string;
18
- logoBorder: string;
19
- };
20
- }
21
- export declare enum NodeType {
22
- MAIN_TASK = "task",
23
- TASK_DEPENDENCY = "task_dependency",
24
- TASK_TRANSFORMATION = "task_transformation",
25
- TASK_DATA = "task_data",
26
- TRANSFORMATION_GROUP = "transformationGroup",
27
- VALIDATION_GROUP = "validationGroup",
28
- PUSH_STEPS_GROUP = "pushStepsGroup",
29
- TRANSFORMATION = "transformation",
30
- VALIDATION = "validation",
31
- PUSH_STEP = "push_step",
32
- DECISION = "decision",
33
- LOOP = "loop",
34
- PROFILE = "profile"
35
- }
36
- export interface FlowDimensions {
37
- width: number;
38
- height: number;
39
22
  }
40
- export declare const NODE_DIMENSIONS: Record<NodeType, FlowDimensions>;
41
- export declare const GRID_SPACING: {
42
- readonly HORIZONTAL: number;
43
- readonly VERTICAL: number;
44
- readonly TASK_DATA_X_GAP: number;
45
- readonly GROUP_Y_OFFSET: number;
46
- };
47
- export declare const TASK_DATA_LAYOUT: {
48
- readonly VALIDATION_NODE_Y_GAP: number;
49
- readonly TRANSFORMATION_NODE_Y_GAP: number;
50
- readonly PUSH_STEP_NODE_Y_GAP: number;
51
- readonly PUSH_STEP_NODE_X_GAP: number;
52
- readonly PUSH_STEP_BRANCH_X_GAP: number;
53
- readonly EXPANDED_GROUP_X: number;
54
- readonly EXPANDED_GROUP_Y_START: number;
55
- readonly TREE_LEVEL_Y_GAP: number;
56
- readonly TREE_SIBLING_X_GAP: number;
57
- readonly TREE_MIN_WIDTH: number;
58
- };
59
23
  export declare const FLOW_EDGE: {
60
24
  readonly width: number;
61
25
  readonly marker: number;
@@ -68,24 +32,25 @@ export declare const FLOW_ICON: {
68
32
  readonly large: number;
69
33
  };
70
34
  /** CSS bindings stay reactive to the source theme/locale without rebuilding a graph. */
71
- export declare function getNodeColorPalette(category: FlowCategory): FlowNodePalette;
72
- export declare function getEdgeColorPalette(category: FlowEdgeCategory): FlowNodePalette;
73
- /** Unknown/custom node types retain the task category, matching VueFlow's default node. */
74
- export declare function getNodeColor(type: string): string;
75
- export type FlowCategoryPart = 'foreground' | 'border' | 'background';
76
- /** Apply a category role to custom semantic content without a consumer-owned recipe. */
77
- export declare function getFlowCategoryStyles(type: string, part?: FlowCategoryPart): CSSProperties;
78
- /** Paired native Tag paint; a category stroke is not an on-bold background. */
79
- export declare function getFlowCategoryBadgeStyles(type: string): Readonly<CSSProperties>;
35
+ export declare function getFlowTonePalette(tone: FlowTone): FlowTonePalette;
36
+ export type FlowTonePart = 'foreground' | 'border' | 'background';
37
+ /** Apply a tone role to custom content without a consumer-owned recipe. */
38
+ export declare function getFlowToneStyles(tone: FlowTone, part?: FlowTonePart): CSSProperties;
39
+ /** Paired native Tag paint; a tone stroke is not an on-bold background. */
40
+ export declare function getFlowToneBadgeStyles(tone: FlowTone): Readonly<CSSProperties>;
80
41
  export interface FlowIconOptions {
81
42
  size?: keyof typeof FLOW_ICON | number;
82
43
  color?: string;
83
44
  disabled?: boolean;
84
45
  }
85
- /** Category glyphs and operational/status glyphs retain distinct source roles. */
86
- export declare function getFlowIconStyles(type: string, options?: FlowIconOptions): CSSProperties;
87
- /** Directed target stroke wins over category fallback; dashes communicate relation kind. */
88
- export declare function getFlowEdgeStyles(category: FlowEdgeCategory, suppliedStyle?: CSSProperties): Styles;
46
+ export type FlowIconRole = 'status-success' | 'status-warning' | 'status-error' | 'status-info' | 'status-loading' | 'control-expand' | 'control-collapse' | 'control-help';
47
+ /** Tone glyphs take the tone's border role; status/control glyphs keep their own source roles. */
48
+ export declare function getFlowIconStyles(role: FlowIconRole | FlowTone, options?: FlowIconOptions): CSSProperties;
49
+ /**
50
+ * SVG presentation recipe for a custom edge path. A supplied directed target stroke wins over
51
+ * the tone fallback; a supplied static dash is kept (the consumer owns what a dash means).
52
+ */
53
+ export declare function getFlowEdgeStyles(tone: FlowTone, suppliedStyle?: CSSProperties): Styles;
89
54
  export declare const FLOW_VIEWPORT: {
90
55
  minZoom: number;
91
56
  maxZoom: number;
@@ -109,12 +74,23 @@ export interface VueFlowTheme {
109
74
  }
110
75
  /** Apply to the actual VueFlow host; import the CSS companion after upstream styles. */
111
76
  export declare function createVueFlowTheme(options?: VueFlowThemeOptions): VueFlowTheme;
112
- /** Preserve target identity for both the visible stroke and arrow marker. */
113
- export declare function styleTargetEdge(edge: Edge, nodes: readonly Node[]): Edge;
77
+ /**
78
+ * Preserve target identity for both the visible stroke and arrow marker. `toneOf` resolves the
79
+ * target node's tone (`blue` when omitted, or when the target is missing).
80
+ */
81
+ export declare function styleTargetEdge(edge: Edge, nodes: readonly Node[], toneOf?: (node: Node) => FlowTone): Edge;
82
+ /** A custom node body: its tone and the consumer-owned fixed geometry. */
83
+ export interface FlowNodeLook {
84
+ tone: FlowTone;
85
+ width: number;
86
+ height: number;
87
+ /** `circle` rounds the body fully; `rectangle` (default) uses `radius.100`. */
88
+ shape?: 'rectangle' | 'circle';
89
+ }
114
90
  export interface FlowNodeState {
115
91
  hovered?: boolean;
116
92
  selected?: boolean;
117
- selfDependency?: boolean;
93
+ dashed?: boolean;
118
94
  }
119
95
  export interface FlowNodeStyles {
120
96
  container: Styles;
@@ -123,11 +99,9 @@ export interface FlowNodeStyles {
123
99
  label: CSSProperties;
124
100
  text: CSSProperties;
125
101
  loadingIcon: CSSProperties;
126
- connectorLogo: CSSProperties;
127
- connectorFlow: CSSProperties;
128
- connectorArrow: CSSProperties;
102
+ logo: CSSProperties;
129
103
  }
130
- /** Custom content and layout share fixed source geometry, not viewport-dependent sizing. */
131
- export declare function getFlowNodeStyles(type: NodeType, state?: FlowNodeState): FlowNodeStyles;
104
+ /** Custom content and layout share fixed consumer geometry, not viewport-dependent sizing. */
105
+ export declare function getFlowNodeStyles(look: FlowNodeLook, state?: FlowNodeState): FlowNodeStyles;
132
106
  /** Minimal detail removes costly decoration, never semantic contrast or graph data. */
133
107
  export declare function getFlowDetailStyles(level: 'minimal' | 'basic' | 'detailed', suppliedStyle: Styles): Styles;
package/dist/index.js CHANGED
@@ -1,46 +1,23 @@
1
1
  import { tokens } from '@beehexa/design-tokens';
2
2
  import { MarkerType } from '@vue-flow/core';
3
- export var NodeType;
4
- (function (NodeType) {
5
- NodeType["MAIN_TASK"] = "task";
6
- NodeType["TASK_DEPENDENCY"] = "task_dependency";
7
- NodeType["TASK_TRANSFORMATION"] = "task_transformation";
8
- NodeType["TASK_DATA"] = "task_data";
9
- NodeType["TRANSFORMATION_GROUP"] = "transformationGroup";
10
- NodeType["VALIDATION_GROUP"] = "validationGroup";
11
- NodeType["PUSH_STEPS_GROUP"] = "pushStepsGroup";
12
- NodeType["TRANSFORMATION"] = "transformation";
13
- NodeType["VALIDATION"] = "validation";
14
- NodeType["PUSH_STEP"] = "push_step";
15
- NodeType["DECISION"] = "decision";
16
- NodeType["LOOP"] = "loop";
17
- NodeType["PROFILE"] = "profile";
18
- })(NodeType || (NodeType = {}));
3
+ /**
4
+ * Generic colour tones. Each tone reads one existing foundation `flow.category.<key>` role set;
5
+ * the foundation keys keep their names and the package gives a tone no domain meaning. The
6
+ * consumer decides which of its own concepts takes which tone.
7
+ */
8
+ export const FLOW_TONES = ['blue', 'red', 'green', 'crimson', 'yellow', 'teal', 'magenta', 'purple'];
9
+ /** Tone → existing foundation role set (unchanged foundation names). */
10
+ const toneRoles = {
11
+ blue: 'flow.category.task', red: 'flow.category.dependency', green: 'flow.category.transformation',
12
+ crimson: 'flow.category.validation', yellow: 'flow.category.pushStep', teal: 'flow.category.decision',
13
+ magenta: 'flow.category.loop', purple: 'flow.category.grouped'
14
+ };
15
+ /** VueFlow's default node colour; the fallback tone wherever the consumer supplies none. */
16
+ const DEFAULT_TONE = 'blue';
19
17
  const numberToken = (name) => {
20
18
  const value = tokens.light[name];
21
19
  return Number.parseFloat(value) * (value.endsWith('rem') ? 16 : 1);
22
20
  };
23
- export const NODE_DIMENSIONS = Object.fromEntries(Object.values(NodeType).map(type => [type, {
24
- width: numberToken(`flow.geometry.${type}.width`),
25
- height: numberToken(`flow.geometry.${type}.height`)
26
- }]));
27
- export const GRID_SPACING = {
28
- HORIZONTAL: numberToken('flow.grid.HORIZONTAL'), VERTICAL: numberToken('flow.grid.VERTICAL'),
29
- TASK_DATA_X_GAP: numberToken('flow.grid.TASK_DATA_X_GAP'),
30
- GROUP_Y_OFFSET: numberToken('flow.grid.GROUP_Y_OFFSET')
31
- };
32
- export const TASK_DATA_LAYOUT = {
33
- VALIDATION_NODE_Y_GAP: numberToken('flow.layout.VALIDATION_NODE_Y_GAP'),
34
- TRANSFORMATION_NODE_Y_GAP: numberToken('flow.layout.TRANSFORMATION_NODE_Y_GAP'),
35
- PUSH_STEP_NODE_Y_GAP: numberToken('flow.layout.PUSH_STEP_NODE_Y_GAP'),
36
- PUSH_STEP_NODE_X_GAP: numberToken('flow.layout.PUSH_STEP_NODE_X_GAP'),
37
- PUSH_STEP_BRANCH_X_GAP: numberToken('flow.layout.PUSH_STEP_BRANCH_X_GAP'),
38
- EXPANDED_GROUP_X: numberToken('flow.layout.EXPANDED_GROUP_X'),
39
- EXPANDED_GROUP_Y_START: numberToken('flow.layout.EXPANDED_GROUP_Y_START'),
40
- TREE_LEVEL_Y_GAP: numberToken('flow.layout.TREE_LEVEL_Y_GAP'),
41
- TREE_SIBLING_X_GAP: numberToken('flow.layout.TREE_SIBLING_X_GAP'),
42
- TREE_MIN_WIDTH: numberToken('flow.layout.TREE_MIN_WIDTH')
43
- };
44
21
  export const FLOW_EDGE = {
45
22
  width: numberToken('flow.edge.width'), marker: numberToken('flow.edge.marker'),
46
23
  radius: numberToken('flow.edge.radius'), handle: numberToken('flow.edge.handle')
@@ -49,51 +26,32 @@ export const FLOW_ICON = {
49
26
  small: numberToken('size.icon.default'), medium: numberToken('size.icon.medium'),
50
27
  large: numberToken('size.icon.large')
51
28
  };
52
- const nodeCategories = {
53
- task: 'task', task_data: 'task', profile: 'task', dependency: 'dependency', task_dependency: 'dependency',
54
- task_transformation: 'transformation', transformation: 'transformation',
55
- transformationGroup: 'transformation', validation: 'validation',
56
- validationGroup: 'validation', push_step: 'pushStep', pushStep: 'pushStep',
57
- pushStepsGroup: 'pushStep', decision: 'decision', loop: 'loop', grouped: 'grouped'
58
- };
59
29
  const variable = (name) => `var(--bh-${name.replaceAll('.', '-')})`;
60
- const palettes = Object.fromEntries([
61
- 'task', 'dependency', 'transformation', 'validation', 'pushStep', 'decision', 'loop', 'grouped'
62
- ].map(category => {
63
- const role = (part) => variable(`flow.category.${category}.${part}`);
64
- return [category, {
30
+ const palettes = Object.fromEntries(FLOW_TONES.map(tone => {
31
+ const role = (part) => variable(`${toneRoles[tone]}.${part}`);
32
+ return [tone, {
65
33
  primary: role('border'), text: role('foreground'), background: role('background'),
66
- border: role('border'), hover: role('hover'),
67
- connector: { bg: role('background'), border: role('border'), logoBorder: role('border') }
34
+ border: role('border'), hover: role('hover')
68
35
  }];
69
36
  }));
70
37
  /** CSS bindings stay reactive to the source theme/locale without rebuilding a graph. */
71
- export function getNodeColorPalette(category) {
72
- return palettes[category];
38
+ export function getFlowTonePalette(tone) {
39
+ return palettes[tone];
73
40
  }
74
- export function getEdgeColorPalette(category) {
75
- return palettes[category === 'dataFlow' ? 'pushStep' :
76
- category === 'conditional' ? 'decision' : category];
77
- }
78
- /** Unknown/custom node types retain the task category, matching VueFlow's default node. */
79
- export function getNodeColor(type) {
80
- return palettes[nodeCategories[type] ?? 'task'].border;
81
- }
82
- /** Apply a category role to custom semantic content without a consumer-owned recipe. */
83
- export function getFlowCategoryStyles(type, part = 'foreground') {
84
- const palette = palettes[nodeCategories[type] ?? 'task'];
41
+ /** Apply a tone role to custom content without a consumer-owned recipe. */
42
+ export function getFlowToneStyles(tone, part = 'foreground') {
43
+ const palette = palettes[tone];
85
44
  if (part === 'border')
86
45
  return { borderColor: palette.border };
87
46
  if (part === 'background')
88
47
  return { backgroundColor: palette.background };
89
48
  return { color: palette.text };
90
49
  }
91
- const categoryBadgeStyles = {};
92
- /** Paired native Tag paint; a category stroke is not an on-bold background. */
93
- export function getFlowCategoryBadgeStyles(type) {
94
- const category = nodeCategories[type] ?? 'task';
95
- const palette = palettes[category];
96
- return categoryBadgeStyles[category] ??= Object.freeze({
50
+ const toneBadgeStyles = {};
51
+ /** Paired native Tag paint; a tone stroke is not an on-bold background. */
52
+ export function getFlowToneBadgeStyles(tone) {
53
+ const palette = palettes[tone];
54
+ return toneBadgeStyles[tone] ??= Object.freeze({
97
55
  color: palette.text,
98
56
  backgroundColor: palette.background,
99
57
  borderColor: palette.border
@@ -105,25 +63,24 @@ const iconRoles = {
105
63
  'status-loading': 'color.icon.info', 'control-expand': 'color.icon',
106
64
  'control-collapse': 'color.icon', 'control-help': 'color.icon'
107
65
  };
108
- /** Category glyphs and operational/status glyphs retain distinct source roles. */
109
- export function getFlowIconStyles(type, options = {}) {
66
+ /** Tone glyphs take the tone's border role; status/control glyphs keep their own source roles. */
67
+ export function getFlowIconStyles(role, options = {}) {
110
68
  const size = options.size ?? 'medium';
111
- const category = type.startsWith('edge-') ? type.slice(5) : type;
112
69
  return {
113
70
  fontSize: `${typeof size === 'number' ? size : FLOW_ICON[size]}px`,
114
71
  color: options.disabled ? variable('color.icon.disabled') :
115
- options.color ?? (iconRoles[type] ? variable(iconRoles[type]) : getNodeColor(category)),
72
+ options.color ?? (role in iconRoles ? variable(iconRoles[role]) : palettes[role].border),
116
73
  display: 'inline-flex', alignItems: 'center', justifyContent: 'center'
117
74
  };
118
75
  }
119
- const edgeDashes = {
120
- dependency: undefined, transformation: '5,5', conditional: '2,2', dataFlow: '8,3,2,3'
121
- };
122
- /** Directed target stroke wins over category fallback; dashes communicate relation kind. */
123
- export function getFlowEdgeStyles(category, suppliedStyle) {
76
+ /**
77
+ * SVG presentation recipe for a custom edge path. A supplied directed target stroke wins over
78
+ * the tone fallback; a supplied static dash is kept (the consumer owns what a dash means).
79
+ */
80
+ export function getFlowEdgeStyles(tone, suppliedStyle) {
124
81
  return {
125
- stroke: suppliedStyle?.stroke ?? getEdgeColorPalette(category).primary,
126
- strokeWidth: FLOW_EDGE.width, strokeDasharray: edgeDashes[category],
82
+ stroke: suppliedStyle?.stroke ?? palettes[tone].border,
83
+ strokeWidth: FLOW_EDGE.width, strokeDasharray: suppliedStyle?.strokeDasharray,
127
84
  fill: 'none', vectorEffect: 'non-scaling-stroke'
128
85
  };
129
86
  }
@@ -135,6 +92,7 @@ export const FLOW_VIEWPORT = {
135
92
  export function createVueFlowTheme(options = {}) {
136
93
  const mode = { ...tokens.light, ...(options.dark ? tokens.dark : {}) };
137
94
  const typography = options.locale === 'vi' ? { ...mode, ...tokens.vi } : mode;
95
+ const nodeTone = options.nodeTone ?? (() => DEFAULT_TONE);
138
96
  return {
139
97
  class: 'bh-vue-flow',
140
98
  style: {
@@ -149,16 +107,19 @@ export function createVueFlowTheme(options = {}) {
149
107
  },
150
108
  flowOptions: { ...FLOW_VIEWPORT, snapGrid: [...FLOW_VIEWPORT.snapGrid] },
151
109
  minimap: {
152
- nodeColor: node => getNodeColor(node.type ?? 'task'),
110
+ nodeColor: node => palettes[nodeTone(node)].border,
153
111
  maskColor: variable('color.surface.scrim'), nodeStrokeColor: variable('color.border.bold')
154
112
  },
155
113
  background: { patternColor: variable('color.border'), bgColor: mode['color.surface.page'] }
156
114
  };
157
115
  }
158
- /** Preserve target identity for both the visible stroke and arrow marker. */
159
- export function styleTargetEdge(edge, nodes) {
116
+ /**
117
+ * Preserve target identity for both the visible stroke and arrow marker. `toneOf` resolves the
118
+ * target node's tone (`blue` when omitted, or when the target is missing).
119
+ */
120
+ export function styleTargetEdge(edge, nodes, toneOf = () => DEFAULT_TONE) {
160
121
  const target = nodes.find(node => node.id === edge.target);
161
- const stroke = getNodeColor(target?.type || target?.data?.nodeType || 'task');
122
+ const stroke = palettes[target ? toneOf(target) : DEFAULT_TONE].border;
162
123
  return {
163
124
  ...edge, animated: false,
164
125
  style: { ...(typeof edge.style === 'object' ? edge.style : {}), stroke },
@@ -170,11 +131,9 @@ export function styleTargetEdge(edge, nodes) {
170
131
  }
171
132
  };
172
133
  }
173
- /** Custom content and layout share fixed source geometry, not viewport-dependent sizing. */
174
- export function getFlowNodeStyles(type, state = {}) {
175
- const dimensions = NODE_DIMENSIONS[type];
176
- const palette = palettes[nodeCategories[type] ?? 'task'];
177
- const round = type === NodeType.LOOP || type === NodeType.DECISION;
134
+ /** Custom content and layout share fixed consumer geometry, not viewport-dependent sizing. */
135
+ export function getFlowNodeStyles(look, state = {}) {
136
+ const palette = palettes[look.tone];
178
137
  const label = {
179
138
  font: variable('font.label.default'), color: palette.text, textAlign: 'center',
180
139
  overflowWrap: 'anywhere', width: '100%'
@@ -185,12 +144,12 @@ export function getFlowNodeStyles(type, state = {}) {
185
144
  };
186
145
  return {
187
146
  container: {
188
- width: `${dimensions.width}px`, height: `${dimensions.height}px`,
147
+ width: `${look.width}px`, height: `${look.height}px`,
189
148
  boxSizing: 'border-box', display: 'flex', flexDirection: 'column',
190
149
  alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
191
150
  border: `${variable('border.width.selected')} solid ${palette.border}`,
192
- borderStyle: state.selfDependency ? 'dashed' : 'solid',
193
- borderRadius: round ? '50%' : variable('radius.100'),
151
+ borderStyle: state.dashed ? 'dashed' : 'solid',
152
+ borderRadius: look.shape === 'circle' ? '50%' : variable('radius.100'),
194
153
  background: state.hovered ? palette.hover : palette.background,
195
154
  color: palette.text, font: variable('font.body.default'),
196
155
  outline: state.selected ? `${variable('border.width.selected')} solid ${variable('color.border.selected')}` : undefined,
@@ -202,9 +161,7 @@ export function getFlowNodeStyles(type, state = {}) {
202
161
  padding: variable('space.150'), gap: variable('space.050')
203
162
  },
204
163
  icon, label, text: label, loadingIcon: icon,
205
- connectorLogo: { borderRadius: variable('radius.075'), objectFit: 'contain' },
206
- connectorFlow: { display: 'flex', alignItems: 'center', gap: variable('space.100') },
207
- connectorArrow: { color: palette.primary, font: variable('font.label.default') }
164
+ logo: { borderRadius: variable('radius.075'), objectFit: 'contain' }
208
165
  };
209
166
  }
210
167
  /** Minimal detail removes costly decoration, never semantic contrast or graph data. */
package/dist/styles.css CHANGED
@@ -71,7 +71,8 @@
71
71
  .bh-vue-flow .vue-flow__edge-textbg { fill: var(--bh-color-surface); }
72
72
  .bh-vue-flow .vue-flow__selection,
73
73
  .bh-vue-flow .vue-flow__nodesselection-rect {
74
- background: var(--bh-color-background-selected);
74
+ /* Transparent: the swept cards stay visible through the box; only the dashed focus border marks it. */
75
+ background: transparent;
75
76
  border: var(--bh-border-width-selected) dashed var(--bh-color-border-focus);
76
77
  }
77
78
  .bh-vue-flow .vue-flow__controls {
@@ -155,8 +156,9 @@
155
156
  .bh-flow-icon[data-bh-loading='true'] { animation: none; }
156
157
  }
157
158
 
158
- /* Labelled relationship markers identify categories, never operational status. */
159
- .bh-flow-category-mark {
159
+ /* Labelled relationship markers identify a colour tone, never operational status. Tones map
160
+ onto the existing foundation `flow.category.*` border roles (see FlowTone in index.ts). */
161
+ .bh-flow-tone-mark {
160
162
  display: inline-block;
161
163
  flex: none;
162
164
  width: var(--bh-space-075);
@@ -164,13 +166,13 @@
164
166
  border-radius: 50%;
165
167
  background: var(--bh-flow-category-task-border);
166
168
  }
167
- .bh-flow-category-mark[data-bh-category='dependency'] { background: var(--bh-flow-category-dependency-border); }
168
- .bh-flow-category-mark[data-bh-category='transformation'] { background: var(--bh-flow-category-transformation-border); }
169
- .bh-flow-category-mark[data-bh-category='validation'] { background: var(--bh-flow-category-validation-border); }
170
- .bh-flow-category-mark[data-bh-category='pushStep'] { background: var(--bh-flow-category-pushStep-border); }
171
- .bh-flow-category-mark[data-bh-category='decision'] { background: var(--bh-flow-category-decision-border); }
172
- .bh-flow-category-mark[data-bh-category='loop'] { background: var(--bh-flow-category-loop-border); }
173
- .bh-flow-category-mark[data-bh-category='grouped'] { background: var(--bh-flow-category-grouped-border); }
169
+ .bh-flow-tone-mark[data-bh-tone='red'] { background: var(--bh-flow-category-dependency-border); }
170
+ .bh-flow-tone-mark[data-bh-tone='green'] { background: var(--bh-flow-category-transformation-border); }
171
+ .bh-flow-tone-mark[data-bh-tone='crimson'] { background: var(--bh-flow-category-validation-border); }
172
+ .bh-flow-tone-mark[data-bh-tone='yellow'] { background: var(--bh-flow-category-pushStep-border); }
173
+ .bh-flow-tone-mark[data-bh-tone='teal'] { background: var(--bh-flow-category-decision-border); }
174
+ .bh-flow-tone-mark[data-bh-tone='magenta'] { background: var(--bh-flow-category-loop-border); }
175
+ .bh-flow-tone-mark[data-bh-tone='purple'] { background: var(--bh-flow-category-grouped-border); }
174
176
 
175
177
  /* Native workflow-canvas theme variables are its public styling API. Scope the host
176
178
  protocol here; never change application-wide primary/text variables. */
@@ -200,6 +202,19 @@
200
202
  --wf-edge: var(--bh-diagram-edge);
201
203
  --wf-edge-active: var(--bh-diagram-selection-ring);
202
204
  --wf-edge-failure: var(--bh-diagram-fail-stroke);
205
+ /* Possible/context/toned-failure cues are shapes, never new dashes or opacity: context is a thinner resting
206
+ stroke (with the native open arrowhead); a toned failure keeps its tone dash and adds a circle-x mark.
207
+ Package-owned variable: it reads the foundation border width, no flow-specific token. */
208
+ --wf-edge-context-width: var(--bh-border-width-default);
209
+ --wf-edge-failure-icon: var(--bh-color-icon-danger);
210
+ /* Generic colour tones for `WorkflowEdge.tone`, mapped onto the existing foundation category border roles.
211
+ Tones are categorical, never execution/failure states; each keeps its static non-colour dash cue. */
212
+ --wf-edge-tone-blue: var(--bh-flow-category-task-border);
213
+ --wf-edge-tone-blue-dash: none;
214
+ --wf-edge-tone-red: var(--bh-flow-category-dependency-border);
215
+ --wf-edge-tone-red-dash: 2 4;
216
+ --wf-edge-tone-green: var(--bh-flow-category-transformation-border);
217
+ --wf-edge-tone-green-dash: 9 3 2 3;
203
218
  --wf-label-bg: var(--bh-diagram-edge-label-bg);
204
219
  --wf-label-fg: var(--bh-diagram-edge-label-text);
205
220
  --wf-label-border: var(--bh-color-border);
@@ -210,12 +225,13 @@
210
225
  --wf-dock-height: calc(var(--bh-size-control-default) + var(--bh-space-100) + 2 * var(--bh-border-width-default));
211
226
  --wf-chrome-radius: var(--bh-radius-050);
212
227
  --wf-node-width: var(--bh-diagram-card-width);
228
+ --wf-node-content-min-width: var(--bh-diagram-card-width);
229
+ --wf-node-content-max-width: calc(2 * var(--bh-diagram-card-width));
213
230
  --wf-node-bg: var(--bh-diagram-node-fill);
214
231
  --wf-node-fg: var(--bh-diagram-node-title);
215
232
  --wf-node-muted: var(--bh-diagram-node-desc);
216
233
  --wf-node-border: var(--bh-diagram-node-border);
217
234
  --wf-node-radius: var(--bh-radius-100);
218
- --wf-node-selected-border: var(--bh-diagram-node-border);
219
235
  --wf-node-ring: var(--bh-diagram-selection-ring);
220
236
  --wf-node-shape-shadow: none;
221
237
  --wf-node-shape-selected: none;
@@ -228,6 +244,19 @@
228
244
  --wf-tone-fg: var(--bh-diagram-tile-glyph);
229
245
  --wf-node-kind-bg: var(--bh-diagram-node-fill);
230
246
  --wf-node-kind-fg: var(--bh-diagram-node-desc);
247
+ /* Group frames (the native `wf-group` node). Package-owned variables over foundation roles; the native layout
248
+ reads padding and header gap from here, so both stay plain lengths. */
249
+ --wf-group-border: var(--bh-color-border);
250
+ --wf-group-border-width: var(--bh-border-width-default);
251
+ --wf-group-radius: var(--bh-radius-card);
252
+ --wf-group-bg: var(--bh-color-surface-subtle);
253
+ --wf-group-nested-bg: var(--bh-color-background-neutral);
254
+ --wf-group-padding: var(--bh-space-stack);
255
+ --wf-group-header-gap: var(--bh-space-150);
256
+ /* The Shift + drag selection box: the same recipe as `.bh-vue-flow .vue-flow__selection` (transparent, dashed). */
257
+ --wf-selection-bg: transparent;
258
+ --wf-selection-border: var(--bh-color-border-focus);
259
+ --wf-selection-border-width: var(--bh-border-width-selected);
231
260
  }
232
261
  /* Opt-in graph contrast uses theme-resolved semantic ink, not a fixed light-mode
233
262
  palette. Preserve selection/failure colours and native edge/arrow pairing. */
@@ -239,11 +268,44 @@
239
268
  --wf-label-fg: var(--bh-color-text);
240
269
  --wf-label-border: var(--bh-color-text);
241
270
  --wf-node-border: var(--bh-color-text);
242
- --wf-node-selected-border: var(--bh-color-border-focus);
243
271
  --wf-node-muted: var(--bh-color-text);
244
272
  --wf-node-kind-fg: var(--bh-color-text);
245
273
  --wf-tone-fg: var(--bh-color-text);
246
- }
274
+ --wf-group-border: var(--bh-color-text);
275
+ }
276
+ /* Execution belongs to the node outline/content; the independent outer ring
277
+ continues to carry selection, hover and keyboard focus. */
278
+ .bh-workflow-canvas .wf-node {
279
+ --wf-node-selected-border: var(--wf-node-border);
280
+ }
281
+ .bh-workflow-canvas .wf-node[data-execution-state='ranOk'] {
282
+ --wf-node-border: var(--bh-diagram-state-ran-ok);
283
+ --wf-node-bg: var(--bh-diagram-ran-ok-fill);
284
+ --wf-node-state-ink: var(--bh-diagram-state-ran-ok);
285
+ }
286
+ .bh-workflow-canvas .wf-node[data-execution-state='ranFailed'] {
287
+ --wf-node-border: var(--bh-diagram-fail-stroke);
288
+ --wf-node-bg: var(--bh-diagram-fail-fill);
289
+ --wf-node-fg: var(--bh-diagram-fail-title);
290
+ --wf-node-muted: var(--bh-diagram-fail-desc);
291
+ }
292
+ .bh-workflow-canvas .wf-node[data-execution-state='neverReached'] {
293
+ --wf-node-border: var(--bh-diagram-unreached-stroke);
294
+ --wf-node-bg: var(--bh-diagram-unreached-fill);
295
+ --wf-node-fg: var(--bh-diagram-unreached-title);
296
+ --wf-node-muted: var(--bh-diagram-unreached-desc);
297
+ }
298
+ .bh-workflow-canvas .wf-node[data-execution-state='branchNotTaken'] {
299
+ --wf-node-border: var(--bh-diagram-nottaken-stroke);
300
+ --wf-node-bg: var(--bh-diagram-nottaken-fill);
301
+ --wf-node-fg: var(--bh-diagram-nottaken-title);
302
+ --wf-node-muted: var(--bh-diagram-nottaken-desc);
303
+ }
304
+ .bh-workflow-canvas .wf-node[data-execution-state='branchNotTaken'] .wf-node__outline {
305
+ stroke-dasharray: 6 3;
306
+ }
307
+ .bh-workflow-canvas .wf-node__execution { color: var(--wf-node-state-ink, var(--wf-node-fg)); }
308
+ .bh-workflow-canvas .wf-node__body { align-items: flex-start; }
247
309
  .bh-workflow-canvas .wf-node__badge {
248
310
  width: var(--bh-diagram-tile-size);
249
311
  height: var(--bh-diagram-tile-size);
@@ -256,17 +318,53 @@
256
318
  padding: 0;
257
319
  background: transparent;
258
320
  }
321
+ /* The native minimum is not enough for a Source title, glyph and run marker.
322
+ Grow to their intrinsic width; the native maximum still wraps long titles. */
323
+ .bh-workflow-canvas .wf-node.wf-node--shape-stadium { width: max-content; }
259
324
  .bh-workflow-canvas .wf-node__title {
260
325
  white-space: normal;
326
+ font: var(--bh-font-heading-xsmall);
327
+ }
328
+ /* A card may break a long unbroken title anywhere. A diamond or ellipse wraps its title and its kind caption at word
329
+ boundaries and never truncates them: its side padding follows its width, so a mid-word break reads as two words and
330
+ an ellipsis hides the node's own name. Only a single word wider than the whole content box breaks. */
331
+ .bh-workflow-canvas .wf-node:not(.wf-node--shape-diamond, .wf-node--shape-ellipse) .wf-node__title {
261
332
  overflow-wrap: anywhere;
262
333
  }
334
+ .bh-workflow-canvas :is(.wf-node--shape-diamond, .wf-node--shape-ellipse) :is(.wf-node__title, .wf-node__kind) {
335
+ max-width: 100%;
336
+ white-space: normal;
337
+ overflow: visible;
338
+ overflow-wrap: break-word;
339
+ text-overflow: clip;
340
+ }
263
341
  .bh-workflow-canvas .wf-node__head { height: auto; }
342
+ .bh-workflow-canvas .wf-node__subtitle {
343
+ font: var(--bh-font-body-default);
344
+ min-height: 0;
345
+ }
346
+ .bh-workflow-canvas :is(.wf-node__kind, .wf-node__caption, .wf-node__indicators) {
347
+ font: var(--bh-font-body-small);
348
+ }
349
+ .bh-workflow-canvas .wf-node__foot { height: auto; }
264
350
  .bh-workflow-canvas .wf-node__logos { padding-block-start: var(--bh-space-050); }
265
351
  /* Native node image geometry has no independent aspect-ratio input. */
266
352
  .bh-workflow-canvas .wf-node__logos > img {
267
353
  width: auto;
268
354
  flex: none;
269
355
  }
356
+ /* Opt-in ordinary-card recipe. The native theme owns the content width mechanism within the
357
+ `--wf-node-content-*` roles bound above; the head and logo flex are global rules here. Title/kind
358
+ wrap and the logo bound keep long titles and every logo readable. */
359
+ .bh-workflow-canvas .wf-node[data-sizing='content']:not(.wf-node--shape-diamond, .wf-node--shape-ellipse)
360
+ :is(.wf-node__title, .wf-node__kind) {
361
+ white-space: normal;
362
+ overflow: visible;
363
+ overflow-wrap: anywhere;
364
+ }
365
+ .bh-workflow-canvas .wf-node[data-sizing='content'] .wf-node__logos > img {
366
+ max-width: 100%;
367
+ }
270
368
  .bh-workflow-canvas .wf-node__execution > svg,
271
369
  .bh-workflow-canvas .wf-node__indicators svg {
272
370
  width: auto;
@@ -288,6 +386,16 @@
288
386
  padding-left: var(--bh-space-050);
289
387
  border-left-width: var(--bh-border-width-default);
290
388
  }
389
+ /* Floating menu surfaces use the overlay role, independently of compact controls. */
390
+ .bh-workflow-canvas .wf-dock__menu {
391
+ border-radius: var(--bh-radius-overlay);
392
+ }
393
+ .bh-workflow-canvas :is(.vue-flow__minimap, .wf-legend__box) {
394
+ border-radius: var(--bh-radius-100);
395
+ }
396
+ .bh-workflow-canvas .wf-shortcuts__dialog {
397
+ border-radius: var(--bh-radius-150);
398
+ }
291
399
  .bh-workflow-canvas .wf-dock__btn {
292
400
  gap: var(--bh-space-050);
293
401
  padding-inline: var(--bh-space-075);
@@ -306,11 +414,57 @@
306
414
  width: auto;
307
415
  height: var(--bh-size-icon-default);
308
416
  }
417
+ /* Native find box (WorkflowFind, the viewer's `/` search). It is an overlay holding a
418
+ search field and result rows: surface.overlay with elevation.shadow.overlay and
419
+ radius.overlay, no border (overlays are set apart by surface and shadow), a
420
+ color.border divider under the field. Rows take the overlay item states one step
421
+ apart: hover surface.overlay.hover; the keyboard-active row (aria-selected)
422
+ surface.overlay.selected plus the border.selected start bar as its non-colour cue;
423
+ that row hovered surface.overlay.selected.hover. Text keeps color.text in every
424
+ state. The shared --wf-list-* roles also paint the jump list and dock menu rows. */
425
+ .bh-workflow-canvas .wf-canvas {
426
+ --wf-list-bg: var(--bh-color-surface-overlay);
427
+ --wf-list-fg: var(--bh-color-text);
428
+ --wf-list-muted: var(--bh-color-text-subtle);
429
+ --wf-list-border: var(--bh-color-border);
430
+ --wf-list-hover: var(--bh-color-surface-overlay-hover);
431
+ --wf-list-active: var(--bh-color-surface-overlay-selected);
432
+ --wf-list-active-hover: var(--bh-color-surface-overlay-selected-hover);
433
+ --wf-list-indicator: var(--bh-color-border-selected);
434
+ --wf-list-indicator-width: var(--bh-border-width-selected);
435
+ --wf-list-failure: var(--bh-color-text-danger);
436
+ --wf-find-border-width: 0;
437
+ --wf-find-radius: var(--bh-radius-overlay);
438
+ --wf-find-input-padding: var(--bh-space-075) var(--bh-space-control-x);
439
+ --wf-find-placeholder: var(--bh-color-text-subtlest);
440
+ --wf-find-list-padding: var(--bh-space-overlay);
441
+ --wf-find-option-padding: var(--bh-space-075) var(--bh-space-item-x);
442
+ --wf-find-option-min-height: var(--bh-size-control-default);
443
+ --wf-find-option-radius: var(--bh-radius-item);
444
+ --wf-find-empty-padding: var(--bh-space-075) var(--bh-space-control-x);
445
+ }
446
+ /* Search field and empty state body.default, result name label.default, the id of a
447
+ node without a name code.default, the kind label body.small in color.text.subtle
448
+ (--wf-list-muted). Names wrap at word boundaries and are never truncated. Focus is
449
+ the field's ring around the whole box (the companion's --wf-focus-outline). */
450
+ .bh-workflow-canvas .wf-find {
451
+ font: var(--bh-font-body-default);
452
+ }
453
+ .bh-workflow-canvas .wf-find__text {
454
+ font: var(--bh-font-label-default);
455
+ }
456
+ .bh-workflow-canvas .wf-find__id {
457
+ font: var(--bh-font-code-default);
458
+ }
459
+ .bh-workflow-canvas .wf-find__detail {
460
+ font: var(--bh-font-body-small);
461
+ }
309
462
  @media (pointer: coarse), (width < 768px) {
310
463
  .bh-workflow-canvas {
311
464
  --wf-control-height: var(--bh-size-target-touch);
312
465
  }
313
466
  .bh-workflow-canvas .wf-canvas {
314
467
  --wf-dock-height: calc(var(--bh-size-target-touch) + var(--bh-space-100) + 2 * var(--bh-border-width-default));
468
+ --wf-find-option-min-height: var(--bh-size-target-touch);
315
469
  }
316
470
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@beehexa/vueflow-tokens",
3
- "version": "2611.5.5",
4
- "description": "Source-backed Beehexa theme, semantic categories and geometry for VueFlow 1",
3
+ "version": "2611.8.1",
4
+ "description": "Source-backed Beehexa theme and generic colour tones for VueFlow 1",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/beehexacorp/beehexa.design.git",
@@ -29,7 +29,7 @@
29
29
  "./package.json": "./package.json"
30
30
  },
31
31
  "dependencies": {
32
- "@beehexa/design-tokens": "2611.5.5"
32
+ "@beehexa/design-tokens": "2611.8.1"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "@vue-flow/core": "^1.0.0",