@beehexa/vueflow-tokens 0.0.0-stage → 2611.5.2

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
@@ -1,3 +1,102 @@
1
- # Temporary Holding Version
1
+ # @beehexa/vueflow-tokens
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Typed ESM Beehexa adapter for VueFlow 1. The authoritative design values are in
4
+ `@beehexa/design-tokens`, not in an application-owned palette. Vue 3 and VueFlow core,
5
+ controls and minimap are peers; no framework source is copied or bundled.
6
+
7
+ ## Integrate
8
+
9
+ Import native Beehexa token CSS once (16px root, 14px body). In the lazy graph entry,
10
+ load upstream core, theme, controls and minimap CSS before `@beehexa/vueflow-tokens/css`.
11
+
12
+ ```ts
13
+ import { createVueFlowTheme } from '@beehexa/vueflow-tokens'
14
+ import '@beehexa/vueflow-tokens/css'
15
+
16
+ const theme = createVueFlowTheme({ dark: false, locale: 'en' })
17
+ ```
18
+
19
+ Bind `theme.class`, `theme.style` and `theme.flowOptions` to the actual `<VueFlow>`; the viewport object is typed with `FlowProps`, not deprecated `FlowOptions`.
20
+ Bind `theme.background` to `<Background>` and `theme.minimap` to `<MiniMap>`; show
21
+ `<Controls>`. Recompute the theme when the theme or locale changes. Keep existing
22
+ ready/error handlers and call the actual viewport `fitView({ padding: 0.2 })` after
23
+ host/dimension changes. The factory uses VueFlow 1's real `fitViewOnInit` option.
24
+
25
+ ## Public contracts
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.
38
+ - `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
43
+ 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
51
+ in `dist/index.d.ts`; CSS has a typed `./css` export.
52
+
53
+ 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.
58
+ Preserve graph IDs, stored coordinates and relationship direction on theme changes.
59
+ Exports must capture the current graph surface/font rather than force white/system fonts.
60
+
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).
66
+
67
+ Bind `data-bh-high-contrast="true"` on the actual `bh-vue-flow` host when the user's
68
+ high-contrast setting is enabled. Native node/edge/path, graph buttons and custom
69
+ `role="button"` focus rings then use `border.width.accent` (4px), retaining the
70
+ source focus colour and offset. The default is `border.width.focused` (2px).
71
+ Bind `data-bh-large-text="true"` and `data-bh-increased-spacing="true"` on that
72
+ same host for the existing user accessibility settings (1.125em node text;
73
+ 1.6 line-height / 0.02em letter-spacing). These are opt-in adaptations, not new
74
+ default typography. `bh-flow-icon[data-bh-loading="true"]` animates only an
75
+ 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.
78
+
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'
83
+ 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.
85
+
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
88
+ 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).
90
+
91
+ ## Build and validation
92
+
93
+ The shared `build-runtime-packages.mjs` compiles strict NodeNext source and checks generated
94
+ output. Actual packed-consumer checks exercise exports/CSS with strict TypeScript and
95
+ `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.
97
+
98
+ Full authored guidance lives in `src/content/tools/vueflow.mdx` and ships through
99
+ `@beehexa/design-rules`. Verify real light/dark and English/Vietnamese surfaces,
100
+ contrast, all fixed dimensions, long labels, selection/focus/error, target-coloured
101
+ markers, controls/minimap, zoom/snap/fit, expanded/cyclic graphs, fullscreen/ESC and PNG
102
+ export. Typed configuration is not visual or accessibility proof.
package/dist/css.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ // CSS is imported for side effects only.
2
+ export {};
@@ -0,0 +1,133 @@
1
+ import type { CSSProperties } from 'vue';
2
+ import { type FlowProps, type Node, type Edge, type Styles } from '@vue-flow/core';
3
+ export interface VueFlowThemeOptions {
4
+ dark?: boolean;
5
+ locale?: 'en' | 'vi';
6
+ }
7
+ export type FlowCategory = 'task' | 'dependency' | 'transformation' | 'validation' | 'pushStep' | 'decision' | 'loop' | 'grouped';
8
+ export type FlowEdgeCategory = 'dependency' | 'transformation' | 'dataFlow' | 'conditional';
9
+ export interface FlowNodePalette {
10
+ primary: string;
11
+ text: string;
12
+ background: string;
13
+ border: string;
14
+ 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
+ }
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
+ export declare const FLOW_EDGE: {
60
+ readonly width: number;
61
+ readonly marker: number;
62
+ readonly radius: number;
63
+ readonly handle: number;
64
+ };
65
+ export declare const FLOW_ICON: {
66
+ readonly small: number;
67
+ readonly medium: number;
68
+ readonly large: number;
69
+ };
70
+ /** 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>;
80
+ export interface FlowIconOptions {
81
+ size?: keyof typeof FLOW_ICON | number;
82
+ color?: string;
83
+ disabled?: boolean;
84
+ }
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;
89
+ export declare const FLOW_VIEWPORT: {
90
+ minZoom: number;
91
+ maxZoom: number;
92
+ snapToGrid: true;
93
+ snapGrid: [number, number];
94
+ fitViewOnInit: true;
95
+ };
96
+ export interface VueFlowTheme {
97
+ class: 'bh-vue-flow';
98
+ style: CSSProperties;
99
+ flowOptions: Partial<FlowProps>;
100
+ minimap: {
101
+ nodeColor: (node: Node) => string;
102
+ maskColor: string;
103
+ nodeStrokeColor: string;
104
+ };
105
+ background: {
106
+ patternColor: string;
107
+ bgColor: string;
108
+ };
109
+ }
110
+ /** Apply to the actual VueFlow host; import the CSS companion after upstream styles. */
111
+ 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;
114
+ export interface FlowNodeState {
115
+ hovered?: boolean;
116
+ selected?: boolean;
117
+ selfDependency?: boolean;
118
+ }
119
+ export interface FlowNodeStyles {
120
+ container: Styles;
121
+ content: CSSProperties;
122
+ icon: CSSProperties;
123
+ label: CSSProperties;
124
+ text: CSSProperties;
125
+ loadingIcon: CSSProperties;
126
+ connectorLogo: CSSProperties;
127
+ connectorFlow: CSSProperties;
128
+ connectorArrow: CSSProperties;
129
+ }
130
+ /** Custom content and layout share fixed source geometry, not viewport-dependent sizing. */
131
+ export declare function getFlowNodeStyles(type: NodeType, state?: FlowNodeState): FlowNodeStyles;
132
+ /** Minimal detail removes costly decoration, never semantic contrast or graph data. */
133
+ export declare function getFlowDetailStyles(level: 'minimal' | 'basic' | 'detailed', suppliedStyle: Styles): Styles;
package/dist/index.js ADDED
@@ -0,0 +1,213 @@
1
+ import { tokens } from '@beehexa/design-tokens';
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 = {}));
19
+ const numberToken = (name) => {
20
+ const value = tokens.light[name];
21
+ return Number.parseFloat(value) * (value.endsWith('rem') ? 16 : 1);
22
+ };
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
+ export const FLOW_EDGE = {
45
+ width: numberToken('flow.edge.width'), marker: numberToken('flow.edge.marker'),
46
+ radius: numberToken('flow.edge.radius'), handle: numberToken('flow.edge.handle')
47
+ };
48
+ export const FLOW_ICON = {
49
+ small: numberToken('size.icon.default'), medium: numberToken('size.icon.medium'),
50
+ large: numberToken('size.icon.large')
51
+ };
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
+ 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, {
65
+ 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') }
68
+ }];
69
+ }));
70
+ /** CSS bindings stay reactive to the source theme/locale without rebuilding a graph. */
71
+ export function getNodeColorPalette(category) {
72
+ return palettes[category];
73
+ }
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'];
85
+ if (part === 'border')
86
+ return { borderColor: palette.border };
87
+ if (part === 'background')
88
+ return { backgroundColor: palette.background };
89
+ return { color: palette.text };
90
+ }
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({
97
+ color: palette.text,
98
+ backgroundColor: palette.background,
99
+ borderColor: palette.border
100
+ });
101
+ }
102
+ const iconRoles = {
103
+ 'status-success': 'color.icon.success', 'status-warning': 'color.icon.warning',
104
+ 'status-error': 'color.icon.danger', 'status-info': 'color.icon.info',
105
+ 'status-loading': 'color.icon.info', 'control-expand': 'color.icon',
106
+ 'control-collapse': 'color.icon', 'control-help': 'color.icon'
107
+ };
108
+ /** Category glyphs and operational/status glyphs retain distinct source roles. */
109
+ export function getFlowIconStyles(type, options = {}) {
110
+ const size = options.size ?? 'medium';
111
+ const category = type.startsWith('edge-') ? type.slice(5) : type;
112
+ return {
113
+ fontSize: `${typeof size === 'number' ? size : FLOW_ICON[size]}px`,
114
+ color: options.disabled ? variable('color.icon.disabled') :
115
+ options.color ?? (iconRoles[type] ? variable(iconRoles[type]) : getNodeColor(category)),
116
+ display: 'inline-flex', alignItems: 'center', justifyContent: 'center'
117
+ };
118
+ }
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) {
124
+ return {
125
+ stroke: suppliedStyle?.stroke ?? getEdgeColorPalette(category).primary,
126
+ strokeWidth: FLOW_EDGE.width, strokeDasharray: edgeDashes[category],
127
+ fill: 'none', vectorEffect: 'non-scaling-stroke'
128
+ };
129
+ }
130
+ export const FLOW_VIEWPORT = {
131
+ minZoom: 0.2, maxZoom: 4, snapToGrid: true,
132
+ snapGrid: [20, 20], fitViewOnInit: true
133
+ };
134
+ /** Apply to the actual VueFlow host; import the CSS companion after upstream styles. */
135
+ export function createVueFlowTheme(options = {}) {
136
+ const mode = { ...tokens.light, ...(options.dark ? tokens.dark : {}) };
137
+ const typography = options.locale === 'vi' ? { ...mode, ...tokens.vi } : mode;
138
+ return {
139
+ class: 'bh-vue-flow',
140
+ style: {
141
+ ...Object.fromEntries(Object.entries(mode).filter(([name]) => name.startsWith('color.') || name.startsWith('flow.category.')).map(([name, value]) => [`--bh-${name.replaceAll('.', '-')}`, value])),
142
+ ...(options.locale === 'vi' ? Object.fromEntries(Object.entries(tokens.vi)
143
+ .map(([name, value]) => [`--bh-${name.replaceAll('.', '-')}`, value])) : {}),
144
+ '--bh-flow-surface': mode['color.surface.page'],
145
+ '--bh-flow-font': typography['font.body.default'],
146
+ '--bh-flow-focus': mode['color.border.focus'],
147
+ '--bh-flow-selected': mode['color.border.selected'],
148
+ '--bh-flow-error': mode['color.border.danger']
149
+ },
150
+ flowOptions: { ...FLOW_VIEWPORT, snapGrid: [...FLOW_VIEWPORT.snapGrid] },
151
+ minimap: {
152
+ nodeColor: node => getNodeColor(node.type ?? 'task'),
153
+ maskColor: variable('color.surface.scrim'), nodeStrokeColor: variable('color.border.bold')
154
+ },
155
+ background: { patternColor: variable('color.border'), bgColor: mode['color.surface.page'] }
156
+ };
157
+ }
158
+ /** Preserve target identity for both the visible stroke and arrow marker. */
159
+ export function styleTargetEdge(edge, nodes) {
160
+ const target = nodes.find(node => node.id === edge.target);
161
+ const stroke = getNodeColor(target?.type || target?.data?.nodeType || 'task');
162
+ return {
163
+ ...edge, animated: false,
164
+ style: { ...(typeof edge.style === 'object' ? edge.style : {}), stroke },
165
+ markerEnd: {
166
+ ...(typeof edge.markerEnd === 'object' ? edge.markerEnd : {
167
+ type: edge.markerEnd === MarkerType.Arrow ? MarkerType.Arrow : MarkerType.ArrowClosed
168
+ }),
169
+ color: stroke, width: FLOW_EDGE.marker, height: FLOW_EDGE.marker
170
+ }
171
+ };
172
+ }
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;
178
+ const label = {
179
+ font: variable('font.label.default'), color: palette.text, textAlign: 'center',
180
+ overflowWrap: 'anywhere', width: '100%'
181
+ };
182
+ const icon = {
183
+ color: palette.primary, fontSize: variable('size.icon.large'),
184
+ display: 'flex', alignItems: 'center', justifyContent: 'center'
185
+ };
186
+ return {
187
+ container: {
188
+ width: `${dimensions.width}px`, height: `${dimensions.height}px`,
189
+ boxSizing: 'border-box', display: 'flex', flexDirection: 'column',
190
+ alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
191
+ border: `${variable('border.width.selected')} solid ${palette.border}`,
192
+ borderStyle: state.selfDependency ? 'dashed' : 'solid',
193
+ borderRadius: round ? '50%' : variable('radius.100'),
194
+ background: state.hovered ? palette.hover : palette.background,
195
+ color: palette.text, font: variable('font.body.default'),
196
+ outline: state.selected ? `${variable('border.width.selected')} solid ${variable('color.border.selected')}` : undefined,
197
+ outlineOffset: variable('state.focus.offset')
198
+ },
199
+ content: {
200
+ display: 'flex', flexDirection: 'column', justifyContent: 'center',
201
+ alignItems: 'center', boxSizing: 'border-box', height: '100%', width: '100%',
202
+ padding: variable('space.150'), gap: variable('space.050')
203
+ },
204
+ 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') }
208
+ };
209
+ }
210
+ /** Minimal detail removes costly decoration, never semantic contrast or graph data. */
211
+ export function getFlowDetailStyles(level, suppliedStyle) {
212
+ return level === 'minimal' ? { ...suppliedStyle, boxShadow: 'none' } : suppliedStyle;
213
+ }
@@ -0,0 +1,301 @@
1
+ .bh-vue-flow.vue-flow {
2
+ background: var(--bh-flow-surface, var(--bh-color-surface-page));
3
+ color: var(--bh-color-text);
4
+ font: var(--bh-flow-font, var(--bh-font-body-default));
5
+ }
6
+ .bh-vue-flow .vue-flow__node {
7
+ --vf-node-bg: var(--bh-color-surface);
8
+ --vf-node-text: var(--bh-color-text);
9
+ --vf-node-color: var(--bh-flow-category-task-border);
10
+ --vf-handle: var(--bh-color-icon);
11
+ --vf-box-shadow: var(--bh-color-border-selected);
12
+ box-sizing: border-box;
13
+ font: inherit;
14
+ color: var(--bh-color-text);
15
+ }
16
+ .bh-vue-flow .vue-flow__node-default,
17
+ .bh-vue-flow .vue-flow__node-input,
18
+ .bh-vue-flow .vue-flow__node-output {
19
+ background: var(--bh-color-surface);
20
+ border: var(--bh-border-width-default) solid var(--bh-color-border-bold);
21
+ border-radius: var(--bh-radius-100);
22
+ padding: var(--bh-space-150);
23
+ }
24
+ .bh-vue-flow .vue-flow__node-default.selectable:hover,
25
+ .bh-vue-flow .vue-flow__node-input.selectable:hover,
26
+ .bh-vue-flow .vue-flow__node-output.selectable:hover {
27
+ box-shadow: var(--bh-elevation-shadow-raised);
28
+ }
29
+ .bh-vue-flow .vue-flow__node.selected {
30
+ outline: var(--bh-border-width-selected) solid var(--bh-flow-selected, var(--bh-color-border-selected));
31
+ outline-offset: var(--bh-state-focus-offset);
32
+ box-shadow: 0 0 0 var(--bh-border-width-accent) var(--bh-color-border-selected);
33
+ }
34
+ .bh-vue-flow .vue-flow__node:focus-visible,
35
+ .bh-vue-flow .vue-flow__node :focus-visible,
36
+ .bh-vue-flow .vue-flow__edge:focus-visible,
37
+ .bh-vue-flow .vue-flow__edge-path:focus-visible,
38
+ .bh-vue-flow :is(button, [role='button']):focus-visible {
39
+ outline: var(--bh-border-width-focused) solid var(--bh-flow-focus, var(--bh-color-border-focus));
40
+ outline-offset: var(--bh-state-focus-offset);
41
+ }
42
+ .bh-vue-flow[data-bh-high-contrast='true'] :is(.vue-flow__node, .vue-flow__edge, .vue-flow__edge-path, button, [role='button']):focus-visible,
43
+ .bh-vue-flow[data-bh-high-contrast='true'] .vue-flow__node :focus-visible {
44
+ outline-width: var(--bh-border-width-accent);
45
+ }
46
+ .bh-vue-flow .vue-flow__node[data-error='true'] {
47
+ border-color: var(--bh-flow-error, var(--bh-color-border-danger));
48
+ border-style: dashed;
49
+ }
50
+ .bh-vue-flow .vue-flow__handle {
51
+ width: var(--bh-flow-edge-handle);
52
+ height: var(--bh-flow-edge-handle);
53
+ border: var(--bh-border-width-selected) solid var(--bh-color-surface);
54
+ background: var(--bh-color-icon);
55
+ }
56
+ .bh-vue-flow .vue-flow__edge-path,
57
+ .bh-vue-flow .vue-flow__connection-path {
58
+ stroke: var(--bh-flow-category-task-border);
59
+ stroke-width: var(--bh-flow-edge-width);
60
+ }
61
+ .bh-vue-flow .vue-flow__edge.updating .vue-flow__edge-path {
62
+ stroke: var(--bh-color-border-focus);
63
+ }
64
+ .bh-vue-flow .vue-flow__edge.selected .vue-flow__edge-path {
65
+ stroke-width: var(--bh-border-width-accent);
66
+ }
67
+ .bh-vue-flow .vue-flow__edge-text {
68
+ fill: var(--bh-color-text);
69
+ font: var(--bh-font-label-small);
70
+ }
71
+ .bh-vue-flow .vue-flow__edge-textbg { fill: var(--bh-color-surface); }
72
+ .bh-vue-flow .vue-flow__selection,
73
+ .bh-vue-flow .vue-flow__nodesselection-rect {
74
+ background: var(--bh-color-background-selected);
75
+ border: var(--bh-border-width-selected) dashed var(--bh-color-border-focus);
76
+ }
77
+ .bh-vue-flow .vue-flow__controls {
78
+ box-sizing: border-box;
79
+ display: flex;
80
+ flex-direction: column;
81
+ gap: var(--bh-space-050);
82
+ padding: var(--bh-space-050);
83
+ background: var(--bh-color-surface);
84
+ border: var(--bh-border-width-default) solid var(--bh-color-border);
85
+ border-radius: var(--bh-radius-100);
86
+ box-shadow: var(--bh-elevation-shadow-raised);
87
+ }
88
+ .bh-vue-flow .vue-flow__controls-button {
89
+ box-sizing: border-box;
90
+ width: var(--bh-size-control-default);
91
+ height: var(--bh-size-control-default);
92
+ padding: var(--bh-space-100);
93
+ background: var(--bh-color-surface);
94
+ color: var(--bh-color-icon);
95
+ fill: currentColor;
96
+ border: 0;
97
+ }
98
+ .bh-vue-flow .vue-flow__controls-button {
99
+ border-radius: var(--bh-radius-050);
100
+ }
101
+ .bh-vue-flow .vue-flow__controls-button:hover {
102
+ background: var(--bh-color-background-neutral-subtle-hover);
103
+ }
104
+ .bh-vue-flow .vue-flow__controls-button:active {
105
+ background: var(--bh-color-background-neutral-subtle-pressed);
106
+ }
107
+ .bh-vue-flow .vue-flow__controls-button:disabled {
108
+ background: var(--bh-color-background-disabled);
109
+ color: var(--bh-color-icon-disabled);
110
+ }
111
+ .bh-vue-flow .vue-flow__controls-button:disabled svg { fill-opacity: 1; }
112
+ .bh-vue-flow .vue-flow__minimap {
113
+ background: var(--bh-color-surface);
114
+ border: var(--bh-border-width-default) solid var(--bh-color-border-bold);
115
+ border-radius: var(--bh-radius-100);
116
+ box-shadow: var(--bh-elevation-shadow-raised);
117
+ overflow: hidden;
118
+ }
119
+ .bh-vue-flow .vue-flow__minimap-mask { fill: var(--bh-color-surface-scrim); }
120
+ .bh-vue-flow .vue-flow__minimap-node { stroke: var(--bh-color-border-bold); }
121
+ .bh-vue-flow .vue-flow__attribution {
122
+ background: var(--bh-color-surface);
123
+ color: var(--bh-color-text-subtle);
124
+ font: var(--bh-font-body-small);
125
+ }
126
+ @media (pointer: coarse) {
127
+ .bh-vue-flow .vue-flow__controls-button {
128
+ width: var(--bh-size-target-touch);
129
+ height: var(--bh-size-target-touch);
130
+ }
131
+ }
132
+ @media (width < 768px) {
133
+ .bh-vue-flow .vue-flow__controls-button {
134
+ width: var(--bh-size-target-touch);
135
+ height: var(--bh-size-target-touch);
136
+ }
137
+ }
138
+
139
+ /* User-selected accessibility adaptations belong to the actual graph host. */
140
+ .bh-vue-flow[data-bh-large-text='true'] .vue-flow__node {
141
+ font-size: 1.125em;
142
+ }
143
+ .bh-vue-flow[data-bh-increased-spacing='true'] .vue-flow__node {
144
+ line-height: 1.6;
145
+ letter-spacing: 0.02em;
146
+ }
147
+
148
+ .bh-flow-icon[data-bh-loading='true'] {
149
+ animation: bh-flow-loading 1s linear infinite;
150
+ }
151
+ @keyframes bh-flow-loading {
152
+ to { transform: rotate(360deg); }
153
+ }
154
+ @media (prefers-reduced-motion: reduce) {
155
+ .bh-flow-icon[data-bh-loading='true'] { animation: none; }
156
+ }
157
+
158
+ /* Labelled relationship markers identify categories, never operational status. */
159
+ .bh-flow-category-mark {
160
+ display: inline-block;
161
+ flex: none;
162
+ width: var(--bh-space-075);
163
+ height: var(--bh-space-075);
164
+ border-radius: 50%;
165
+ background: var(--bh-flow-category-task-border);
166
+ }
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); }
174
+
175
+ /* Native workflow-canvas theme variables are its public styling API. Scope the host
176
+ protocol here; never change application-wide primary/text variables. */
177
+ .bh-workflow-canvas {
178
+ --background: var(--bh-color-surface-page);
179
+ --foreground: var(--bh-color-text);
180
+ --card: var(--bh-color-surface);
181
+ --popover: var(--bh-color-surface-overlay);
182
+ --muted: var(--bh-color-background-neutral);
183
+ --muted-foreground: var(--bh-color-text-subtle);
184
+ --primary: var(--bh-color-border-focus);
185
+ --destructive: var(--bh-color-border-danger);
186
+ --border: var(--bh-color-border);
187
+ --radius: var(--bh-radius-050);
188
+ --wf-font-mono: var(--bh-font-family-mono);
189
+ --wf-control-height: var(--bh-size-control-default);
190
+ --wf-dock-radius: var(--bh-radius-100);
191
+ --wf-focus-outline: var(--bh-border-width-focused) solid var(--bh-color-border-focus);
192
+ --wf-focus-outline-offset: var(--bh-state-focus-offset);
193
+ --wf-chrome-shadow: var(--bh-elevation-shadow-overlay);
194
+ color: var(--bh-color-text);
195
+ font: var(--bh-font-body-default);
196
+ }
197
+ .bh-workflow-canvas .wf-canvas {
198
+ --wf-bg: var(--bh-diagram-canvas);
199
+ --wf-grid: var(--bh-diagram-grid);
200
+ --wf-edge: var(--bh-diagram-edge);
201
+ --wf-edge-active: var(--bh-diagram-selection-ring);
202
+ --wf-edge-failure: var(--bh-diagram-fail-stroke);
203
+ --wf-label-bg: var(--bh-diagram-edge-label-bg);
204
+ --wf-label-fg: var(--bh-diagram-edge-label-text);
205
+ --wf-label-border: var(--bh-color-border);
206
+ --wf-chrome-bg: var(--bh-color-surface);
207
+ --wf-chrome-fg: var(--bh-color-text);
208
+ --wf-chrome-hover: var(--bh-color-background-neutral-subtle-hover);
209
+ --wf-chrome-border: var(--bh-color-border-bold);
210
+ --wf-dock-height: calc(var(--bh-size-control-default) + var(--bh-space-100) + 2 * var(--bh-border-width-default));
211
+ --wf-chrome-radius: var(--bh-radius-050);
212
+ --wf-node-width: var(--bh-diagram-card-width);
213
+ --wf-node-bg: var(--bh-diagram-node-fill);
214
+ --wf-node-fg: var(--bh-diagram-node-title);
215
+ --wf-node-muted: var(--bh-diagram-node-desc);
216
+ --wf-node-border: var(--bh-diagram-node-border);
217
+ --wf-node-radius: var(--bh-radius-100);
218
+ --wf-node-selected-border: var(--bh-diagram-node-border);
219
+ --wf-node-ring: var(--bh-diagram-selection-ring);
220
+ --wf-node-shape-shadow: none;
221
+ --wf-node-shape-selected: none;
222
+ --wf-node-padding-block: var(--bh-diagram-padding);
223
+ --wf-node-column-gap: var(--bh-space-150);
224
+ --wf-node-row-gap: var(--bh-space-050);
225
+ --wf-node-status-size: var(--bh-size-icon-default);
226
+ --wf-node-logo-size: var(--bh-content-partner-logo-image-size);
227
+ --wf-tone-bg: var(--bh-diagram-tile-bg);
228
+ --wf-tone-fg: var(--bh-diagram-tile-glyph);
229
+ --wf-node-kind-bg: var(--bh-diagram-node-fill);
230
+ --wf-node-kind-fg: var(--bh-diagram-node-desc);
231
+ }
232
+ .bh-workflow-canvas .wf-node__badge {
233
+ width: var(--bh-diagram-tile-size);
234
+ height: var(--bh-diagram-tile-size);
235
+ }
236
+ .bh-workflow-canvas .wf-node__badge > svg {
237
+ width: auto;
238
+ height: var(--bh-diagram-glyph-size);
239
+ }
240
+ .bh-workflow-canvas .wf-node__content > .wf-node__kind {
241
+ padding: 0;
242
+ background: transparent;
243
+ }
244
+ .bh-workflow-canvas .wf-node__title {
245
+ white-space: normal;
246
+ overflow-wrap: anywhere;
247
+ }
248
+ .bh-workflow-canvas .wf-node__head { height: auto; }
249
+ .bh-workflow-canvas .wf-node__logos { padding-block-start: var(--bh-space-050); }
250
+ /* Native node image geometry has no independent aspect-ratio input. */
251
+ .bh-workflow-canvas .wf-node__logos > img {
252
+ width: auto;
253
+ flex: none;
254
+ }
255
+ .bh-workflow-canvas .wf-node__execution > svg,
256
+ .bh-workflow-canvas .wf-node__indicators svg {
257
+ width: auto;
258
+ }
259
+
260
+ /* Native dock geometry has fixed-pixel gaps/type and opacity-only disabled paint,
261
+ without corresponding variables. Keep its actual controls, but scale this
262
+ shared compact family through the same Source roles as native AntD docks. */
263
+ .bh-workflow-canvas .wf-dock__bar {
264
+ gap: var(--bh-space-025);
265
+ padding: var(--bh-space-050);
266
+ border-width: var(--bh-border-width-default);
267
+ }
268
+ .bh-workflow-canvas .wf-dock__group {
269
+ gap: var(--bh-space-025);
270
+ }
271
+ .bh-workflow-canvas .wf-dock__group + .wf-dock__group {
272
+ margin-left: var(--bh-space-025);
273
+ padding-left: var(--bh-space-050);
274
+ border-left-width: var(--bh-border-width-default);
275
+ }
276
+ .bh-workflow-canvas .wf-dock__btn {
277
+ gap: var(--bh-space-050);
278
+ padding-inline: var(--bh-space-075);
279
+ font: var(--bh-font-label-small);
280
+ }
281
+ .bh-workflow-canvas .wf-dock__btn:not(:disabled):active {
282
+ background: var(--bh-color-background-neutral-subtle-pressed);
283
+ }
284
+ .bh-workflow-canvas .wf-dock__btn:disabled {
285
+ opacity: 1;
286
+ color: var(--bh-color-text-disabled);
287
+ background: transparent;
288
+ cursor: not-allowed;
289
+ }
290
+ .bh-workflow-canvas .wf-dock__icon {
291
+ width: auto;
292
+ height: var(--bh-size-icon-default);
293
+ }
294
+ @media (pointer: coarse), (width < 768px) {
295
+ .bh-workflow-canvas {
296
+ --wf-control-height: var(--bh-size-target-touch);
297
+ }
298
+ .bh-workflow-canvas .wf-canvas {
299
+ --wf-dock-height: calc(var(--bh-size-target-touch) + var(--bh-space-100) + 2 * var(--bh-border-width-default));
300
+ }
301
+ }
package/package.json CHANGED
@@ -1,6 +1,51 @@
1
1
  {
2
2
  "name": "@beehexa/vueflow-tokens",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "2611.5.2",
4
+ "description": "Source-backed Beehexa theme, semantic categories and geometry for VueFlow 1",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/beehexacorp/beehexa.design.git",
8
+ "directory": "packages/vueflow-tokens"
9
+ },
10
+ "type": "module",
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "files": [
14
+ "dist",
15
+ "README.md"
16
+ ],
17
+ "sideEffects": [
18
+ "**/*.css"
19
+ ],
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ },
25
+ "./css": {
26
+ "types": "./dist/css.d.ts",
27
+ "default": "./dist/styles.css"
28
+ },
29
+ "./package.json": "./package.json"
30
+ },
31
+ "dependencies": {
32
+ "@beehexa/design-tokens": "2611.5.2"
33
+ },
34
+ "peerDependencies": {
35
+ "@vue-flow/core": "^1.0.0",
36
+ "@vue-flow/controls": "^1.0.0",
37
+ "@vue-flow/minimap": "^1.0.0",
38
+ "vue": "^3.0.0"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "scripts": {
44
+ "build": "node ../../scripts/build-runtime-packages.mjs vueflow-tokens",
45
+ "check": "node ../../scripts/build-runtime-packages.mjs vueflow-tokens --check",
46
+ "prepack": "npm run build"
47
+ },
48
+ "engines": {
49
+ "node": ">=22.22.0"
50
+ }
51
+ }