@hlang-tech/editor-ui 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +78 -2
- package/lib/canvas/CanvasChrome.css +45 -0
- package/lib/canvas/CanvasDock.css +82 -0
- package/lib/canvas/EmptyState.css +23 -0
- package/lib/command-palette/NodeSearch.css +153 -0
- package/lib/context-menu/NodeContextMenu.css +102 -0
- package/lib/edges/FlowEdge.css +80 -0
- package/lib/index.d.mts +971 -0
- package/lib/index.d.ts +971 -0
- package/lib/index.js +2402 -0
- package/lib/index.js.map +1 -0
- package/lib/index.mjs +2318 -0
- package/lib/index.mjs.map +1 -0
- package/lib/nodes/AssetNode.css +146 -0
- package/lib/nodes/HlangNode.css +192 -0
- package/lib/nodes/PillNode.css +185 -0
- package/lib/overlays/Cheatsheet.css +107 -0
- package/lib/overlays/Toast.css +34 -0
- package/lib/panels/AssetGrid.css +38 -0
- package/lib/panels/InspectorPanel.css +161 -0
- package/lib/panels/OutlineTree.css +208 -0
- package/lib/panels/ParamForm.css +88 -0
- package/lib/panels/PositionPanel.css +87 -0
- package/lib/panels/PreviewPanel.css +51 -0
- package/lib/panels/RailTabs.css +34 -0
- package/lib/shell/EditorShell.css +75 -0
- package/lib/styles.css +33 -0
- package/lib/tokens/tokens.css +100 -0
- package/lib/toolbar/ToolCluster.css +48 -0
- package/lib/toolbar/Toolbar.css +126 -0
- package/package.json +97 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 hlang-tech
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,79 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/hlang-tech/hlang/main/media/logo.png" width="88" alt="hlang">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
# @hlang-tech/editor-ui
|
|
6
|
+
|
|
7
|
+
The Lumen design system for hlang: React components for a node editor built on React Flow and [`@hlang-tech/editor-react`](https://www.npmjs.com/package/@hlang-tech/editor-react). Black and silver surfaces, image and intent nodes, live edges, and the panels around the canvas.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@hlang-tech/editor-ui)
|
|
10
|
+
[](https://github.com/hlang-tech/hlang/blob/main/LICENSE)
|
|
11
|
+
|
|
12
|
+
> **Status:** 0.x, in active development. The API can change in any minor release.
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<img src="https://raw.githubusercontent.com/hlang-tech/hlang/main/media/lumen-editor.png" alt="The Lumen editor built with editor-ui" width="100%">
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm install @hlang-tech/editor-ui @hlang-tech/editor-react @hlang-tech/editor-core @xyflow/react react react-dom
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The same build is published as `@hlang-org/editor-ui`. Import the stylesheet once:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import '@xyflow/react/dist/style.css';
|
|
28
|
+
import '@hlang-tech/editor-ui/styles.css';
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { ReactFlow, ReactFlowProvider } from '@xyflow/react';
|
|
35
|
+
import { useHlangFlow } from '@hlang-tech/editor-react';
|
|
36
|
+
import { createHlangFlowTypes } from '@hlang-tech/editor-ui';
|
|
37
|
+
|
|
38
|
+
const { nodeTypes, edgeTypes } = createHlangFlowTypes();
|
|
39
|
+
|
|
40
|
+
function Canvas({ core }) {
|
|
41
|
+
const flow = useHlangFlow(core);
|
|
42
|
+
return <ReactFlow {...flow} nodeTypes={nodeTypes} edgeTypes={edgeTypes} colorMode="dark" fitView />;
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`createHlangFlowTypes` registers the node and edge components for the three node kinds. A node renders as a framed image when its `imageUrl` parameter is set, and as a pill otherwise. An edge shows the source node's `role` parameter as a label. The runtime contract does not change: every node is still a Readable, Transform, or Writable node with ports.
|
|
47
|
+
|
|
48
|
+
See the [playground](https://github.com/hlang-tech/hlang/tree/main/examples/editor-react-playground) for a complete editor.
|
|
49
|
+
|
|
50
|
+
## Components
|
|
51
|
+
|
|
52
|
+
| Area | Exports |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| Nodes and edges | `HlangNode`, `AssetNodeChrome`, `PillNodeChrome`, `NodeChrome`, `PortRow`, `SignalBar`, `NodeBodySlot`, `FlowEdge`, `createHlangFlowTypes` |
|
|
55
|
+
| Shell and toolbar | `EditorShell`, `Toolbar`, `UndoRedo`, `RunButton`, `ToolbarStat`, `ToolbarGhostButton`, `ToolCluster` |
|
|
56
|
+
| Canvas | `CanvasChrome`, `CanvasDock`, `EmptyState` |
|
|
57
|
+
| Panels | `OutlineTree`, `RailTabs`, `AssetGrid`, `InspectorPanel`, `ParamForm`, `PositionPanel`, `PreviewPanel`, `DiagnosticBadge` |
|
|
58
|
+
| Creating nodes | `NodeSearch`, `useNodeSearch`, `useNodeSearchTriggers` (double-click the canvas, or drop a connection on empty space) |
|
|
59
|
+
| Interaction | `useEditorHotkeys`, `NodeContextMenu`, `useNodeContextMenu`, `NodeInteractionProvider`, `Cheatsheet`, `ToastProvider` |
|
|
60
|
+
| Live execution | `LiveSignalProvider`, `LiveSignalStore`, `useLiveSignals`, `useNodeLiveState`, `useEdgeFlowing` |
|
|
61
|
+
| Theme | `TOKEN_NAMES`, `tokenVar`, `createTheme`, `EditorUIThemeProvider` |
|
|
62
|
+
|
|
63
|
+
### Keyboard shortcuts
|
|
64
|
+
|
|
65
|
+
`useEditorHotkeys(core)` binds undo (`⌘Z`), redo (`⇧⌘Z` or `⌘Y`), copy, cut, paste, duplicate (`⌘D`), select all (`⌘A`), and delete (`Delete` or `Backspace`). `Ctrl` replaces `⌘` on Windows and Linux. Shortcuts are ignored while a text field has focus.
|
|
66
|
+
|
|
67
|
+
### Live execution
|
|
68
|
+
|
|
69
|
+
`useLiveSignals(core, handle)` feeds a run's activity and errors into a `LiveSignalStore`. Nodes and edges subscribe by id, so only the elements whose state changes re-render.
|
|
70
|
+
|
|
71
|
+
### Theming
|
|
72
|
+
|
|
73
|
+
All colors, fonts, and radii are CSS custom properties (`--hl-*`). Override them with `createTheme({ 'hl-silver': '#d8dce4' })` passed as a `style`, or with `EditorUIThemeProvider`.
|
|
74
|
+
|
|
75
|
+
Source and documentation: [github.com/hlang-tech/hlang](https://github.com/hlang-tech/hlang).
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Theme React Flow's built-in controls + minimap to the design tokens. */
|
|
2
|
+
|
|
3
|
+
.hl-controls.react-flow__controls {
|
|
4
|
+
border-radius: 10px;
|
|
5
|
+
overflow: hidden;
|
|
6
|
+
border: 1px solid var(--hl-line);
|
|
7
|
+
box-shadow: none;
|
|
8
|
+
}
|
|
9
|
+
.hl-controls .react-flow__controls-button {
|
|
10
|
+
background: var(--hl-surface);
|
|
11
|
+
border-bottom: 1px solid var(--hl-line);
|
|
12
|
+
color: var(--hl-ink-2);
|
|
13
|
+
}
|
|
14
|
+
.hl-controls .react-flow__controls-button:hover {
|
|
15
|
+
background: var(--hl-surface-2);
|
|
16
|
+
color: var(--hl-ink);
|
|
17
|
+
}
|
|
18
|
+
.hl-controls .react-flow__controls-button svg {
|
|
19
|
+
fill: currentColor;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* A discreet frosted minimap that recedes into the canvas at rest and lifts on
|
|
23
|
+
hover — furniture, not a focal point. */
|
|
24
|
+
.hl-minimap.react-flow__minimap {
|
|
25
|
+
width: 168px;
|
|
26
|
+
height: 118px;
|
|
27
|
+
background: var(--hl-glass);
|
|
28
|
+
backdrop-filter: blur(var(--hl-glass-blur)) saturate(var(--hl-glass-sat));
|
|
29
|
+
-webkit-backdrop-filter: blur(var(--hl-glass-blur)) saturate(var(--hl-glass-sat));
|
|
30
|
+
border: 1px solid var(--hl-line);
|
|
31
|
+
border-radius: 12px;
|
|
32
|
+
box-shadow: var(--hl-glass-edge), 0 8px 24px -12px rgba(0, 0, 0, 0.7);
|
|
33
|
+
opacity: 0.42;
|
|
34
|
+
transition: opacity 0.2s var(--hl-ease);
|
|
35
|
+
}
|
|
36
|
+
.hl-minimap.react-flow__minimap:hover {
|
|
37
|
+
opacity: 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Connection line while dragging out a new edge. */
|
|
41
|
+
.react-flow__connection-path {
|
|
42
|
+
stroke: var(--hl-silver);
|
|
43
|
+
stroke-width: 1.5;
|
|
44
|
+
filter: drop-shadow(0 0 4px color-mix(in oklab, var(--hl-silver) 50%, transparent));
|
|
45
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** CanvasDock — centred bottom glass toolbar (pan/zoom/add), black & silver. */
|
|
2
|
+
|
|
3
|
+
.hl-dock {
|
|
4
|
+
position: absolute;
|
|
5
|
+
z-index: 8;
|
|
6
|
+
bottom: 20px;
|
|
7
|
+
left: 50%;
|
|
8
|
+
transform: translateX(-50%);
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
gap: 3px;
|
|
12
|
+
padding: 5px;
|
|
13
|
+
background: var(--hl-glass-2);
|
|
14
|
+
backdrop-filter: blur(var(--hl-glass-blur)) saturate(var(--hl-glass-sat));
|
|
15
|
+
-webkit-backdrop-filter: blur(var(--hl-glass-blur)) saturate(var(--hl-glass-sat));
|
|
16
|
+
border: 1px solid var(--hl-line-strong);
|
|
17
|
+
border-radius: var(--hl-r-pill);
|
|
18
|
+
box-shadow: var(--hl-glass-edge), 0 14px 34px -18px rgba(0, 0, 0, 0.85);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.hl-dock-btn {
|
|
22
|
+
width: 34px;
|
|
23
|
+
height: 34px;
|
|
24
|
+
display: grid;
|
|
25
|
+
place-items: center;
|
|
26
|
+
border: none;
|
|
27
|
+
background: transparent;
|
|
28
|
+
color: var(--hl-ink-2);
|
|
29
|
+
border-radius: var(--hl-r-pill);
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
transition: background 0.14s var(--hl-ease), color 0.14s var(--hl-ease);
|
|
32
|
+
}
|
|
33
|
+
.hl-dock-btn:hover:not(:disabled) {
|
|
34
|
+
background: rgba(255, 255, 255, 0.07);
|
|
35
|
+
color: var(--hl-ink);
|
|
36
|
+
}
|
|
37
|
+
.hl-dock-btn:disabled {
|
|
38
|
+
opacity: 0.3;
|
|
39
|
+
cursor: not-allowed;
|
|
40
|
+
}
|
|
41
|
+
/* The active interaction mode reads in silver. */
|
|
42
|
+
.hl-dock-btn.is-active {
|
|
43
|
+
background: color-mix(in oklab, var(--hl-silver) 22%, transparent);
|
|
44
|
+
color: var(--hl-silver-hi);
|
|
45
|
+
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--hl-silver) 30%, transparent);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Add-node is the one lightly-emphasised action. */
|
|
49
|
+
.hl-dock-add {
|
|
50
|
+
color: var(--hl-ink);
|
|
51
|
+
}
|
|
52
|
+
.hl-dock-add:hover {
|
|
53
|
+
background: color-mix(in oklab, var(--hl-silver) 18%, transparent);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.hl-dock-glyph {
|
|
57
|
+
font-size: 17px;
|
|
58
|
+
line-height: 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.hl-dock-sep {
|
|
62
|
+
width: 1px;
|
|
63
|
+
height: 20px;
|
|
64
|
+
margin: 0 4px;
|
|
65
|
+
background: var(--hl-line-strong);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.hl-dock-zoom {
|
|
69
|
+
min-width: 48px;
|
|
70
|
+
height: 34px;
|
|
71
|
+
padding: 0 8px;
|
|
72
|
+
border: none;
|
|
73
|
+
background: transparent;
|
|
74
|
+
color: var(--hl-ink);
|
|
75
|
+
font-size: 12px;
|
|
76
|
+
border-radius: var(--hl-r-sm);
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
transition: background 0.14s var(--hl-ease);
|
|
79
|
+
}
|
|
80
|
+
.hl-dock-zoom:hover {
|
|
81
|
+
background: rgba(255, 255, 255, 0.07);
|
|
82
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
.hl-empty {
|
|
2
|
+
position: absolute;
|
|
3
|
+
inset: 0;
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
gap: 8px;
|
|
9
|
+
pointer-events: none;
|
|
10
|
+
}
|
|
11
|
+
.hl-empty-title {
|
|
12
|
+
margin: 0;
|
|
13
|
+
font-size: 17px;
|
|
14
|
+
font-weight: 550;
|
|
15
|
+
letter-spacing: -0.01em;
|
|
16
|
+
color: var(--hl-ink-2);
|
|
17
|
+
}
|
|
18
|
+
.hl-empty-hint {
|
|
19
|
+
margin: 0;
|
|
20
|
+
font-family: var(--hl-font-mono);
|
|
21
|
+
font-size: 12px;
|
|
22
|
+
color: var(--hl-ink-3);
|
|
23
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NodeSearch palette — a floating deep-space glass command menu. Anchored at a
|
|
3
|
+
* fixed screen point; a full-viewport scrim catches outside clicks. Electric-
|
|
4
|
+
* blue focus accent, kind-tinted result chips, fuzzy-match emphasis.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
.hl-node-search-scrim {
|
|
8
|
+
position: fixed;
|
|
9
|
+
inset: 0;
|
|
10
|
+
z-index: 40;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.hl-node-search {
|
|
14
|
+
position: fixed;
|
|
15
|
+
z-index: 41;
|
|
16
|
+
width: 320px;
|
|
17
|
+
max-height: 360px;
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
/* Nearly opaque so canvas content does not bleed through and muddy the
|
|
21
|
+
panel's layering; the slight translucency + blur keeps it feeling like
|
|
22
|
+
glass floating above the graph, not a flat modal. */
|
|
23
|
+
background: color-mix(in oklab, var(--hl-surface) 98%, transparent);
|
|
24
|
+
border: 1px solid var(--hl-line-strong);
|
|
25
|
+
border-radius: var(--hl-r-lg);
|
|
26
|
+
box-shadow: var(--hl-shadow-node), 0 24px 48px -24px rgba(0, 0, 0, 0.9);
|
|
27
|
+
backdrop-filter: blur(20px) saturate(1.3);
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
color: var(--hl-ink);
|
|
30
|
+
font-family: var(--hl-font-ui);
|
|
31
|
+
animation: hl-node-search-in 140ms var(--hl-ease);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@keyframes hl-node-search-in {
|
|
35
|
+
from {
|
|
36
|
+
opacity: 0;
|
|
37
|
+
transform: translateY(-4px) scale(0.98);
|
|
38
|
+
}
|
|
39
|
+
to {
|
|
40
|
+
opacity: 1;
|
|
41
|
+
transform: translateY(0) scale(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.hl-node-search-field {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: 8px;
|
|
49
|
+
padding: 10px 12px;
|
|
50
|
+
border-bottom: 1px solid var(--hl-line);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.hl-node-search-glyph {
|
|
54
|
+
color: var(--hl-silver);
|
|
55
|
+
font-size: 13px;
|
|
56
|
+
line-height: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.hl-node-search-input {
|
|
60
|
+
flex: 1;
|
|
61
|
+
min-width: 0;
|
|
62
|
+
background: transparent;
|
|
63
|
+
border: none;
|
|
64
|
+
outline: none;
|
|
65
|
+
color: var(--hl-ink);
|
|
66
|
+
/* UI font (not mono) to sit consistently with the result labels below;
|
|
67
|
+
mono is reserved for the kind chips and port tags where it reads as code. */
|
|
68
|
+
font-family: var(--hl-font-ui);
|
|
69
|
+
font-size: 14px;
|
|
70
|
+
letter-spacing: 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.hl-node-search-input::placeholder {
|
|
74
|
+
color: var(--hl-ink-3);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.hl-node-search-list {
|
|
78
|
+
list-style: none;
|
|
79
|
+
margin: 0;
|
|
80
|
+
padding: 6px;
|
|
81
|
+
overflow-y: auto;
|
|
82
|
+
display: flex;
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
gap: 2px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.hl-node-search-empty {
|
|
88
|
+
padding: 12px;
|
|
89
|
+
color: var(--hl-ink-3);
|
|
90
|
+
font-size: 12px;
|
|
91
|
+
text-align: center;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.hl-node-search-item {
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
gap: 10px;
|
|
98
|
+
padding: 8px 10px;
|
|
99
|
+
border-radius: var(--hl-r-sm);
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
transition: background-color 120ms var(--hl-ease);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.hl-node-search-item.is-active {
|
|
105
|
+
background: var(--hl-surface-2);
|
|
106
|
+
box-shadow: inset 0 0 0 1px var(--hl-line-strong);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.hl-node-search-kind {
|
|
110
|
+
flex: none;
|
|
111
|
+
width: 20px;
|
|
112
|
+
height: 20px;
|
|
113
|
+
display: grid;
|
|
114
|
+
place-items: center;
|
|
115
|
+
border-radius: var(--hl-r-sm);
|
|
116
|
+
font-size: 11px;
|
|
117
|
+
font-weight: 600;
|
|
118
|
+
color: var(--hl-void);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.hl-node-search-kind.hl-kind-r {
|
|
122
|
+
background: var(--hl-r);
|
|
123
|
+
}
|
|
124
|
+
.hl-node-search-kind.hl-kind-t {
|
|
125
|
+
background: var(--hl-t);
|
|
126
|
+
}
|
|
127
|
+
.hl-node-search-kind.hl-kind-w {
|
|
128
|
+
background: var(--hl-w);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.hl-node-search-label {
|
|
132
|
+
flex: 1;
|
|
133
|
+
min-width: 0;
|
|
134
|
+
font-size: 13px;
|
|
135
|
+
white-space: nowrap;
|
|
136
|
+
overflow: hidden;
|
|
137
|
+
text-overflow: ellipsis;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.hl-node-search-hit {
|
|
141
|
+
background: transparent;
|
|
142
|
+
color: var(--hl-silver);
|
|
143
|
+
font-weight: 600;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.hl-node-search-port {
|
|
147
|
+
flex: none;
|
|
148
|
+
font-size: 11px;
|
|
149
|
+
color: var(--hl-ink-3);
|
|
150
|
+
padding: 2px 6px;
|
|
151
|
+
border: 1px solid var(--hl-line);
|
|
152
|
+
border-radius: 999px;
|
|
153
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NodeContextMenu — a compact black/silver liquid-glass menu, matching the
|
|
3
|
+
* command palette's material. Frosted panel, silver active row, muted-coral
|
|
4
|
+
* destructive row, monospace shortcut hints.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
.hl-ctx-scrim {
|
|
8
|
+
position: fixed;
|
|
9
|
+
inset: 0;
|
|
10
|
+
z-index: 40;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.hl-ctx-menu {
|
|
14
|
+
position: fixed;
|
|
15
|
+
z-index: 41;
|
|
16
|
+
min-width: 192px;
|
|
17
|
+
padding: 5px;
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
gap: 1px;
|
|
21
|
+
background: var(--hl-glass-2);
|
|
22
|
+
backdrop-filter: blur(var(--hl-glass-blur)) saturate(var(--hl-glass-sat));
|
|
23
|
+
-webkit-backdrop-filter: blur(var(--hl-glass-blur)) saturate(var(--hl-glass-sat));
|
|
24
|
+
border: 1px solid var(--hl-line-strong);
|
|
25
|
+
border-radius: var(--hl-r-md);
|
|
26
|
+
box-shadow: var(--hl-glass-edge), var(--hl-shadow-node),
|
|
27
|
+
0 20px 44px -22px rgba(0, 0, 0, 0.9);
|
|
28
|
+
outline: none;
|
|
29
|
+
animation: hl-ctx-in 0.13s var(--hl-ease);
|
|
30
|
+
}
|
|
31
|
+
@keyframes hl-ctx-in {
|
|
32
|
+
from {
|
|
33
|
+
opacity: 0;
|
|
34
|
+
transform: translateY(-4px) scale(0.98);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.hl-ctx-item {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
gap: 9px;
|
|
42
|
+
width: 100%;
|
|
43
|
+
padding: 7px 9px;
|
|
44
|
+
border: none;
|
|
45
|
+
background: transparent;
|
|
46
|
+
color: var(--hl-ink);
|
|
47
|
+
font-family: var(--hl-font-ui);
|
|
48
|
+
font-size: 12.5px;
|
|
49
|
+
text-align: left;
|
|
50
|
+
border-radius: var(--hl-r-sm);
|
|
51
|
+
cursor: pointer;
|
|
52
|
+
transition: background 0.12s var(--hl-ease), color 0.12s var(--hl-ease);
|
|
53
|
+
}
|
|
54
|
+
/* Silver active row (keyboard/hover). */
|
|
55
|
+
.hl-ctx-item.is-active {
|
|
56
|
+
background: color-mix(in oklab, var(--hl-silver) 18%, transparent);
|
|
57
|
+
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--hl-silver) 26%, transparent);
|
|
58
|
+
}
|
|
59
|
+
.hl-ctx-item:disabled {
|
|
60
|
+
color: var(--hl-ink-3);
|
|
61
|
+
cursor: default;
|
|
62
|
+
}
|
|
63
|
+
.hl-ctx-item.is-danger {
|
|
64
|
+
color: var(--hl-error);
|
|
65
|
+
}
|
|
66
|
+
.hl-ctx-item.is-danger.is-active {
|
|
67
|
+
background: color-mix(in oklab, var(--hl-error) 20%, transparent);
|
|
68
|
+
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--hl-error) 34%, transparent);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.hl-ctx-item-icon {
|
|
72
|
+
flex: none;
|
|
73
|
+
width: 16px;
|
|
74
|
+
height: 16px;
|
|
75
|
+
display: grid;
|
|
76
|
+
place-items: center;
|
|
77
|
+
color: var(--hl-silver-lo);
|
|
78
|
+
}
|
|
79
|
+
.hl-ctx-item.is-danger .hl-ctx-item-icon {
|
|
80
|
+
color: var(--hl-error);
|
|
81
|
+
}
|
|
82
|
+
.hl-ctx-item.is-active .hl-ctx-item-icon {
|
|
83
|
+
color: var(--hl-silver-hi);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.hl-ctx-item-label {
|
|
87
|
+
flex: 1;
|
|
88
|
+
min-width: 0;
|
|
89
|
+
white-space: nowrap;
|
|
90
|
+
overflow: hidden;
|
|
91
|
+
text-overflow: ellipsis;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.hl-ctx-item-key {
|
|
95
|
+
flex: none;
|
|
96
|
+
font-size: 11px;
|
|
97
|
+
color: var(--hl-ink-3);
|
|
98
|
+
letter-spacing: 0.02em;
|
|
99
|
+
}
|
|
100
|
+
.hl-ctx-item.is-active .hl-ctx-item-key {
|
|
101
|
+
color: var(--hl-ink-2);
|
|
102
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Living-stream edge — THE signature. At rest an edge is a quiet silver
|
|
3
|
+
* hairline. While a flow is running, a cold spark marches along it O -> I —
|
|
4
|
+
* the one bold, memorable, hlang-unique moment (a static board can't do this).
|
|
5
|
+
* `value` sources show a solid overlay (change-only, no marching). Flow is
|
|
6
|
+
* gated by `.is-flowing` (driven by live signals). Reduced motion disables the
|
|
7
|
+
* march (handled globally in tokens.css).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
.hl-edge-base {
|
|
11
|
+
stroke: var(--hl-line-strong);
|
|
12
|
+
stroke-width: 1.25;
|
|
13
|
+
fill: none;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* Resting overlay: a calm silver line, no glow, no march. */
|
|
17
|
+
.hl-edge-flow {
|
|
18
|
+
stroke: color-mix(in oklab, var(--hl-silver) 55%, transparent);
|
|
19
|
+
stroke-width: 1.25;
|
|
20
|
+
stroke-linecap: round;
|
|
21
|
+
fill: none;
|
|
22
|
+
opacity: 0.8;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Live (hot) streams march; value streams are solid. */
|
|
26
|
+
.hl-edge.is-live .hl-edge-flow {
|
|
27
|
+
stroke-dasharray: 2 10;
|
|
28
|
+
animation: hl-flow 0.9s linear infinite;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* During execution, an edge whose source just emitted lights with the cold
|
|
32
|
+
spark and quickens — the visible living-stream pulse (via useLiveSignals). */
|
|
33
|
+
.hl-edge.is-flowing .hl-edge-flow {
|
|
34
|
+
stroke: var(--hl-spark);
|
|
35
|
+
stroke-width: 1.75;
|
|
36
|
+
opacity: 1;
|
|
37
|
+
filter: drop-shadow(0 0 6px color-mix(in oklab, var(--hl-spark) 70%, transparent));
|
|
38
|
+
animation-duration: 0.4s;
|
|
39
|
+
}
|
|
40
|
+
.hl-edge.is-error.is-flowing .hl-edge-flow {
|
|
41
|
+
stroke: var(--hl-error);
|
|
42
|
+
filter: drop-shadow(0 0 6px color-mix(in oklab, var(--hl-error) 70%, transparent));
|
|
43
|
+
}
|
|
44
|
+
.hl-edge.is-value .hl-edge-flow {
|
|
45
|
+
stroke-dasharray: none;
|
|
46
|
+
opacity: 0.5;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.hl-edge.is-error .hl-edge-flow {
|
|
50
|
+
stroke: color-mix(in oklab, var(--hl-error) 60%, transparent);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.hl-edge:hover .hl-edge-base {
|
|
54
|
+
stroke: color-mix(in oklab, var(--hl-silver) 45%, var(--hl-line-strong));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@keyframes hl-flow {
|
|
58
|
+
to {
|
|
59
|
+
stroke-dashoffset: -12;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Semantic label riding the edge midpoint — a frosted glass pill naming what
|
|
64
|
+
the wire carries (Character / Background …). Reads the source node's role. */
|
|
65
|
+
.hl-edge-label {
|
|
66
|
+
position: absolute;
|
|
67
|
+
pointer-events: none;
|
|
68
|
+
z-index: 6; /* ride above node cards so a short-edge label is never clipped */
|
|
69
|
+
font-size: 10px;
|
|
70
|
+
letter-spacing: 0.02em;
|
|
71
|
+
color: var(--hl-ink);
|
|
72
|
+
background: var(--hl-glass-2);
|
|
73
|
+
backdrop-filter: blur(10px) saturate(1.1);
|
|
74
|
+
-webkit-backdrop-filter: blur(10px) saturate(1.1);
|
|
75
|
+
border: 1px solid color-mix(in oklab, var(--hl-silver) 22%, var(--hl-line));
|
|
76
|
+
border-radius: var(--hl-r-pill);
|
|
77
|
+
padding: 2px 9px;
|
|
78
|
+
box-shadow: var(--hl-glass-edge), 0 2px 8px -3px rgba(0, 0, 0, 0.6);
|
|
79
|
+
white-space: nowrap;
|
|
80
|
+
}
|