@syware/jlib 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/all.js +2 -2
- package/dist/atoms/jl-input.d.ts +1 -0
- package/dist/atoms/jl-input.js +2 -0
- package/dist/atoms/jl-select.d.ts +6 -0
- package/dist/atoms/jl-select.js +18 -1
- package/dist/atoms/jl-switch.d.ts +12 -1
- package/dist/atoms/jl-switch.js +11 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/lib/flow.d.ts +10 -1
- package/dist/lib/flow.js +26 -9
- package/dist/molecules/jl-accordion-item.css_raw.js +1 -1
- package/dist/molecules/jl-accordion-item.d.ts +6 -1
- package/dist/molecules/jl-accordion-item.js +21 -6
- package/dist/molecules/jl-search.d.ts +3 -1
- package/dist/molecules/jl-search.js +8 -6
- package/dist/molecules/jl-segmented.css_raw.js +1 -1
- package/dist/molecules/jl-segmented.js +14 -7
- package/dist/molecules/jl-stat.css_raw.js +1 -1
- package/dist/molecules/jl-stat.d.ts +6 -0
- package/dist/molecules/jl-stat.js +6 -0
- package/dist/organisms/jl-flow.css_raw.js +1 -1
- package/dist/organisms/jl-flow.d.ts +28 -5
- package/dist/organisms/jl-flow.js +138 -33
- package/dist/types/jl-flow.types.d.ts +17 -0
- package/dist/types/jl-segmented.types.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,6 +107,7 @@ git push --follow-tags
|
|
|
107
107
|
|
|
108
108
|
The tag stages the version on npm and creates the GitHub release; a maintainer then approves it with
|
|
109
109
|
`npm stage approve`. Every step, pre-releases and what to do when something fails: [RELEASING.md](RELEASING.md).
|
|
110
|
+
What changed in each version: [CHANGELOG.md](CHANGELOG.md).
|
|
110
111
|
|
|
111
112
|
## Made by SyWare
|
|
112
113
|
|
package/dist/all.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TOKEN_STYLES } from "./styles/shared.js";
|
|
1
|
+
import { BASE_STYLES, CONTROL_STYLES, FLOATING_STYLES, TOKEN_STYLES } from "./styles/shared.js";
|
|
2
2
|
import { adoptTokens } from "./lib/tokens.js";
|
|
3
3
|
import { JlElement } from "./base/jl-element.js";
|
|
4
4
|
import { JlFormControl } from "./base/jl-form-control.js";
|
|
@@ -73,4 +73,4 @@ import { JlSplitView, SPLIT_ORIENTATIONS } from "./templates/jl-split-view.js";
|
|
|
73
73
|
// src/all.ts
|
|
74
74
|
registerIcons(COMMON_ICONS);
|
|
75
75
|
define(JlAccordion, JlAccordionItem, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlCodeBlock, JlCombobox, JlCommandPalette, JlConfirm, JlCopyButton, JlDatePicker, JlDialog, JlDivider, JlDrawer, JlEmpty, JlField, JlFileDrop, JlFlow, JlIcon, JlInput, JlKbd, JlLink, JlListItem, JlMenu, JlNavbar, JlNumberInput, JlPageHeader, JlPagination, JlPopover, JlProgress, JlProgressRing, JlRadioGroup, JlRating, JlSearch, JlSegmented, JlSelect, JlSideNav, JlSkeleton, JlSlider, JlSpinner, JlSplitView, JlStat, JlSteps, JlSwitch, JlTable, JlTabPanel, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree);
|
|
76
|
-
export { ACCORDION_VARIANTS, AVATAR_SHAPES, AVATAR_SIZES, AVATAR_STATUSES, BUTTON_TYPES, BUTTON_VARIANTS, CARD_PADDINGS, CHIP_SIZES, CODE_LANGUAGES, COMMON_ICONS, CONFIRM_TONES, CONTROL_SIZES, DIALOG_SIZES, DIVIDER_ALIGNS, DRAWER_PLACEMENTS, DRAWER_SIZES, EMPTY_SIZES, FIELD_ORIENTATIONS, INPUT_TYPES, JlAccordion, JlAccordionItem, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlCodeBlock, JlCombobox, JlCommandPalette, JlConfirm, JlCopyButton, JlDatePicker, JlDialog, JlDivider, JlDrawer, JlElement, JlEmpty, JlField, JlFileDrop, JlFlow, JlFormControl, JlIcon, JlInput, JlKbd, JlLink, JlListItem, JlMenu, JlNavbar, JlNumberInput, JlPageHeader, JlPagination, JlPopover, JlProgress, JlProgressRing, JlRadioGroup, JlRating, JlSearch, JlSegmented, JlSelect, JlSideNav, JlSkeleton, JlSlider, JlSpinner, JlSplitView, JlStat, JlSteps, JlSwitch, JlTabPanel, JlTable, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree, LINK_VARIANTS, PLACEMENTS, PROGRESS_SIZES, PROGRESS_TONES, RADIO_GROUP_ORIENTATIONS, RADIO_GROUP_VARIANTS, SKELETON_SHAPES, SPLIT_ORIENTATIONS, STAT_TRENDS, STEPS_ORIENTATIONS, SWITCH_SIZES, TABLE_SORT_DIRECTIONS, TAB_VARIANTS, TOASTER_PLACEMENTS, TOAST_TONES, TOKEN_STYLES, TONES, TONE_ICONS, TONE_ICON_FILLED, adoptTokens, clampedNumber, computePosition, confirmDialog, define, followAnchor, hasIcon, iconElement, iconNames, jsonOf, oneOf, placeFloating, registerIcons, toast };
|
|
76
|
+
export { ACCORDION_VARIANTS, AVATAR_SHAPES, AVATAR_SIZES, AVATAR_STATUSES, BASE_STYLES, BUTTON_TYPES, BUTTON_VARIANTS, CARD_PADDINGS, CHIP_SIZES, CODE_LANGUAGES, COMMON_ICONS, CONFIRM_TONES, CONTROL_SIZES, CONTROL_STYLES, DIALOG_SIZES, DIVIDER_ALIGNS, DRAWER_PLACEMENTS, DRAWER_SIZES, EMPTY_SIZES, FIELD_ORIENTATIONS, FLOATING_STYLES, INPUT_TYPES, JlAccordion, JlAccordionItem, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlCodeBlock, JlCombobox, JlCommandPalette, JlConfirm, JlCopyButton, JlDatePicker, JlDialog, JlDivider, JlDrawer, JlElement, JlEmpty, JlField, JlFileDrop, JlFlow, JlFormControl, JlIcon, JlInput, JlKbd, JlLink, JlListItem, JlMenu, JlNavbar, JlNumberInput, JlPageHeader, JlPagination, JlPopover, JlProgress, JlProgressRing, JlRadioGroup, JlRating, JlSearch, JlSegmented, JlSelect, JlSideNav, JlSkeleton, JlSlider, JlSpinner, JlSplitView, JlStat, JlSteps, JlSwitch, JlTabPanel, JlTable, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree, LINK_VARIANTS, PLACEMENTS, PROGRESS_SIZES, PROGRESS_TONES, RADIO_GROUP_ORIENTATIONS, RADIO_GROUP_VARIANTS, SKELETON_SHAPES, SPLIT_ORIENTATIONS, STAT_TRENDS, STEPS_ORIENTATIONS, SWITCH_SIZES, TABLE_SORT_DIRECTIONS, TAB_VARIANTS, TOASTER_PLACEMENTS, TOAST_TONES, TOKEN_STYLES, TONES, TONE_ICONS, TONE_ICON_FILLED, adoptTokens, clampedNumber, computePosition, confirmDialog, define, followAnchor, hasIcon, iconElement, iconNames, jsonOf, oneOf, placeFloating, registerIcons, toast };
|
package/dist/atoms/jl-input.d.ts
CHANGED
package/dist/atoms/jl-input.js
CHANGED
|
@@ -33,6 +33,7 @@ var PASSWORD = "password";
|
|
|
33
33
|
var REVEALED = "text";
|
|
34
34
|
var ICON_SIZE = 16;
|
|
35
35
|
var ACTION_ICON_SIZE = 14;
|
|
36
|
+
var ENTER = "Enter";
|
|
36
37
|
/**
|
|
37
38
|
* A single-line text field that takes part in forms. `value` is the text; `input` fires as people type and
|
|
38
39
|
* `change` when they commit. Slots `start` and `end` hold adornments such as a unit or a prefix.
|
|
@@ -137,6 +138,7 @@ var JlInput = class extends JlFormControl {
|
|
|
137
138
|
this.events.emit("input", native.value);
|
|
138
139
|
});
|
|
139
140
|
this.on(native, "change", () => this.events.emit("change", native.value));
|
|
141
|
+
this.on(native, "keydown", (event) => event.key === ENTER && this.events.emit("enter", native.value));
|
|
140
142
|
this.on(this._refs.clear, "click", () => this.clearValue());
|
|
141
143
|
this.on(this._refs.reveal, "click", () => this.toggleReveal());
|
|
142
144
|
}
|
|
@@ -15,6 +15,7 @@ export declare class JlSelect extends JlFormControl {
|
|
|
15
15
|
get(): SelectOption[];
|
|
16
16
|
set(setter: SelectOption[] | ((prevState: SelectOption[]) => SelectOption[])): void;
|
|
17
17
|
}>>;
|
|
18
|
+
/** The chosen value. One that no option has is kept, as an option of its own, rather than replaced. */
|
|
18
19
|
readonly value: Readonly<Readonly<{
|
|
19
20
|
get(): string;
|
|
20
21
|
set(setter: string | ((prevState: string) => string)): void;
|
|
@@ -60,6 +61,11 @@ export declare class JlSelect extends JlFormControl {
|
|
|
60
61
|
private optionNodes;
|
|
61
62
|
private placeholderNodes;
|
|
62
63
|
private renderOptions;
|
|
64
|
+
/**
|
|
65
|
+
* A value no option has (a choice since removed, options still loading) shows as an option of its own, first, so
|
|
66
|
+
* the field shows what is saved and the form keeps it, rather than another choice.
|
|
67
|
+
*/
|
|
68
|
+
private keepUnknownValue;
|
|
63
69
|
/** Shows the chosen option; the form gets what is shown. */
|
|
64
70
|
private renderValue;
|
|
65
71
|
/**
|
package/dist/atoms/jl-select.js
CHANGED
|
@@ -40,7 +40,8 @@ var JlSelect = class extends JlFormControl {
|
|
|
40
40
|
attribute: "options",
|
|
41
41
|
parse: jsonOf([])
|
|
42
42
|
});
|
|
43
|
-
value
|
|
43
|
+
/** The chosen value. One that no option has is kept, as an option of its own, rather than replaced. */
|
|
44
|
+
value = this.useChange(NO_VALUE, () => this.renderOptions(), { attribute: "value" });
|
|
44
45
|
/** Shown while nothing is chosen; it cannot be picked back from the list. */
|
|
45
46
|
placeholder = this.useChange("", () => this.renderOptions(), { attribute: "placeholder" });
|
|
46
47
|
size = this.useChange("md", (value) => this.reflect("size", value), {
|
|
@@ -120,8 +121,24 @@ var JlSelect = class extends JlFormControl {
|
|
|
120
121
|
}
|
|
121
122
|
renderOptions() {
|
|
122
123
|
this._refs.native.replaceChildren(...this.placeholderNodes(), ...this.optionNodes());
|
|
124
|
+
this.keepUnknownValue();
|
|
123
125
|
this.renderValue();
|
|
124
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* A value no option has (a choice since removed, options still loading) shows as an option of its own, first, so
|
|
129
|
+
* the field shows what is saved and the form keeps it, rather than another choice.
|
|
130
|
+
*/
|
|
131
|
+
keepUnknownValue() {
|
|
132
|
+
const native = this._refs.native;
|
|
133
|
+
const value = this.value.get();
|
|
134
|
+
const known = [...native.options].some((option) => option.value === value);
|
|
135
|
+
if (value === NO_VALUE || known) return;
|
|
136
|
+
const afterPlaceholder = this.placeholder.get() === "" ? 0 : 1;
|
|
137
|
+
native.add(/* @__PURE__ */ jsx("option", {
|
|
138
|
+
value,
|
|
139
|
+
children: value
|
|
140
|
+
}), afterPlaceholder);
|
|
141
|
+
}
|
|
125
142
|
/** Shows the chosen option; the form gets what is shown. */
|
|
126
143
|
renderValue() {
|
|
127
144
|
const native = this._refs.native;
|
|
@@ -24,6 +24,14 @@ export declare class JlSwitch extends JlFormControl {
|
|
|
24
24
|
get(): string;
|
|
25
25
|
set(setter: string | ((prevState: string) => string)): void;
|
|
26
26
|
}>>;
|
|
27
|
+
/**
|
|
28
|
+
* Keeps the label for screen readers only, for a switch whose meaning the page already shows beside it (an "On"
|
|
29
|
+
* or "Off" next to the track).
|
|
30
|
+
*/
|
|
31
|
+
readonly hideLabel: Readonly<Readonly<{
|
|
32
|
+
get(): boolean;
|
|
33
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
34
|
+
}>>;
|
|
27
35
|
readonly size: Readonly<Readonly<{
|
|
28
36
|
get(): SwitchSize;
|
|
29
37
|
set(setter: ((prevState: SwitchSize) => SwitchSize) | SwitchSize): void;
|
|
@@ -52,6 +60,9 @@ export declare class JlSwitch extends JlFormControl {
|
|
|
52
60
|
private commit;
|
|
53
61
|
private renderChecked;
|
|
54
62
|
private renderFormValue;
|
|
55
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Hides the text column when there is nothing to show, so a lone switch has no gap beside it; a hidden label stays
|
|
65
|
+
* for screen readers, out of the layout.
|
|
66
|
+
*/
|
|
56
67
|
private renderText;
|
|
57
68
|
}
|
package/dist/atoms/jl-switch.js
CHANGED
|
@@ -7,6 +7,7 @@ import { jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
|
7
7
|
// src/atoms/jl-switch.tsx
|
|
8
8
|
var SWITCH_SIZES = ["md", "sm"];
|
|
9
9
|
var DEFAULT_VALUE = "on";
|
|
10
|
+
var SCREEN_READER_ONLY = "sr-only";
|
|
10
11
|
/**
|
|
11
12
|
* An on/off setting that applies at once, such as "Dark mode" or "Email me". `checked` is the state; the form gets
|
|
12
13
|
* `value` while it is on. For a choice confirmed later with a button, prefer a checkbox.
|
|
@@ -27,6 +28,11 @@ var JlSwitch = class extends JlFormControl {
|
|
|
27
28
|
this._refs.description.hidden = value === "";
|
|
28
29
|
this.renderText();
|
|
29
30
|
}, { attribute: "description" });
|
|
31
|
+
/**
|
|
32
|
+
* Keeps the label for screen readers only, for a switch whose meaning the page already shows beside it (an "On"
|
|
33
|
+
* or "Off" next to the track).
|
|
34
|
+
*/
|
|
35
|
+
hideLabel = this.useChange(false, () => this.renderText(), { attribute: "hide-label" });
|
|
30
36
|
size = this.useChange("md", (value) => this.reflect("size", value), {
|
|
31
37
|
attribute: "size",
|
|
32
38
|
parse: oneOf(SWITCH_SIZES, "md")
|
|
@@ -115,10 +121,14 @@ var JlSwitch = class extends JlFormControl {
|
|
|
115
121
|
renderFormValue() {
|
|
116
122
|
this.internals.setFormValue(this.checked.get() ? this.value.get() : null);
|
|
117
123
|
}
|
|
118
|
-
/**
|
|
124
|
+
/**
|
|
125
|
+
* Hides the text column when there is nothing to show, so a lone switch has no gap beside it; a hidden label stays
|
|
126
|
+
* for screen readers, out of the layout.
|
|
127
|
+
*/
|
|
119
128
|
renderText() {
|
|
120
129
|
const slotted = this._refs.slot.assignedNodes().some((node) => node instanceof Element || node.textContent?.trim() !== "");
|
|
121
130
|
this._refs.text.hidden = this.label.get() === "" && this.description.get() === "" && !slotted;
|
|
131
|
+
this._refs.text.classList.toggle(SCREEN_READER_ONLY, this.hideLabel.get());
|
|
122
132
|
}
|
|
123
133
|
};
|
|
124
134
|
JlSwitch.register();
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { COMMON_ICONS } from './lib/icon-set';
|
|
|
7
7
|
export { hasIcon, iconElement, iconNames, registerIcons } from './lib/icons';
|
|
8
8
|
export { CONTROL_SIZES, TONE_ICON_FILLED, TONE_ICONS, TONES } from './lib/options';
|
|
9
9
|
export { adoptTokens } from './lib/tokens';
|
|
10
|
-
export { TOKEN_STYLES } from './styles/shared';
|
|
10
|
+
export { BASE_STYLES, CONTROL_STYLES, FLOATING_STYLES, TOKEN_STYLES } from './styles/shared';
|
|
11
11
|
export type * from './types/calendar.types';
|
|
12
12
|
export type * from './types/files.types';
|
|
13
13
|
export type * from './types/floating.types';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TOKEN_STYLES } from "./styles/shared.js";
|
|
1
|
+
import { BASE_STYLES, CONTROL_STYLES, FLOATING_STYLES, TOKEN_STYLES } from "./styles/shared.js";
|
|
2
2
|
import { adoptTokens } from "./lib/tokens.js";
|
|
3
3
|
import { JlElement } from "./base/jl-element.js";
|
|
4
4
|
import { JlFormControl } from "./base/jl-form-control.js";
|
|
@@ -70,4 +70,4 @@ import { JlToaster, TOASTER_PLACEMENTS, toast } from "./organisms/jl-toaster.js"
|
|
|
70
70
|
import { JlTree } from "./organisms/jl-tree.js";
|
|
71
71
|
import { JlAppShell } from "./templates/jl-app-shell.js";
|
|
72
72
|
import { JlSplitView, SPLIT_ORIENTATIONS } from "./templates/jl-split-view.js";
|
|
73
|
-
export { ACCORDION_VARIANTS, AVATAR_SHAPES, AVATAR_SIZES, AVATAR_STATUSES, BUTTON_TYPES, BUTTON_VARIANTS, CARD_PADDINGS, CHIP_SIZES, CODE_LANGUAGES, COMMON_ICONS, CONFIRM_TONES, CONTROL_SIZES, DIALOG_SIZES, DIVIDER_ALIGNS, DRAWER_PLACEMENTS, DRAWER_SIZES, EMPTY_SIZES, FIELD_ORIENTATIONS, INPUT_TYPES, JlAccordion, JlAccordionItem, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlCodeBlock, JlCombobox, JlCommandPalette, JlConfirm, JlCopyButton, JlDatePicker, JlDialog, JlDivider, JlDrawer, JlElement, JlEmpty, JlField, JlFileDrop, JlFlow, JlFormControl, JlIcon, JlInput, JlKbd, JlLink, JlListItem, JlMenu, JlNavbar, JlNumberInput, JlPageHeader, JlPagination, JlPopover, JlProgress, JlProgressRing, JlRadioGroup, JlRating, JlSearch, JlSegmented, JlSelect, JlSideNav, JlSkeleton, JlSlider, JlSpinner, JlSplitView, JlStat, JlSteps, JlSwitch, JlTabPanel, JlTable, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree, LINK_VARIANTS, PLACEMENTS, PROGRESS_SIZES, PROGRESS_TONES, RADIO_GROUP_ORIENTATIONS, RADIO_GROUP_VARIANTS, SKELETON_SHAPES, SPLIT_ORIENTATIONS, STAT_TRENDS, STEPS_ORIENTATIONS, SWITCH_SIZES, TABLE_SORT_DIRECTIONS, TAB_VARIANTS, TOASTER_PLACEMENTS, TOAST_TONES, TOKEN_STYLES, TONES, TONE_ICONS, TONE_ICON_FILLED, adoptTokens, clampedNumber, computePosition, confirmDialog, define, followAnchor, hasIcon, iconElement, iconNames, jsonOf, oneOf, placeFloating, registerIcons, toast };
|
|
73
|
+
export { ACCORDION_VARIANTS, AVATAR_SHAPES, AVATAR_SIZES, AVATAR_STATUSES, BASE_STYLES, BUTTON_TYPES, BUTTON_VARIANTS, CARD_PADDINGS, CHIP_SIZES, CODE_LANGUAGES, COMMON_ICONS, CONFIRM_TONES, CONTROL_SIZES, CONTROL_STYLES, DIALOG_SIZES, DIVIDER_ALIGNS, DRAWER_PLACEMENTS, DRAWER_SIZES, EMPTY_SIZES, FIELD_ORIENTATIONS, FLOATING_STYLES, INPUT_TYPES, JlAccordion, JlAccordionItem, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlCodeBlock, JlCombobox, JlCommandPalette, JlConfirm, JlCopyButton, JlDatePicker, JlDialog, JlDivider, JlDrawer, JlElement, JlEmpty, JlField, JlFileDrop, JlFlow, JlFormControl, JlIcon, JlInput, JlKbd, JlLink, JlListItem, JlMenu, JlNavbar, JlNumberInput, JlPageHeader, JlPagination, JlPopover, JlProgress, JlProgressRing, JlRadioGroup, JlRating, JlSearch, JlSegmented, JlSelect, JlSideNav, JlSkeleton, JlSlider, JlSpinner, JlSplitView, JlStat, JlSteps, JlSwitch, JlTabPanel, JlTable, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree, LINK_VARIANTS, PLACEMENTS, PROGRESS_SIZES, PROGRESS_TONES, RADIO_GROUP_ORIENTATIONS, RADIO_GROUP_VARIANTS, SKELETON_SHAPES, SPLIT_ORIENTATIONS, STAT_TRENDS, STEPS_ORIENTATIONS, SWITCH_SIZES, TABLE_SORT_DIRECTIONS, TAB_VARIANTS, TOASTER_PLACEMENTS, TOAST_TONES, TOKEN_STYLES, TONES, TONE_ICONS, TONE_ICON_FILLED, adoptTokens, clampedNumber, computePosition, confirmDialog, define, followAnchor, hasIcon, iconElement, iconNames, jsonOf, oneOf, placeFloating, registerIcons, toast };
|
package/dist/lib/flow.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { FlowEdge, FlowEdgeEnds, FlowGraph, FlowNode, FlowPoint, FlowPort, FlowViewport } from '../types/jl-flow.types';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* A node's box: fixed, so that every anchor and edge is computed without measuring the page. A node with a detail
|
|
4
|
+
* line is taller.
|
|
5
|
+
*/
|
|
3
6
|
export declare const FLOW_NODE: {
|
|
4
7
|
readonly height: 64;
|
|
8
|
+
readonly heightWithDetail: 96;
|
|
5
9
|
readonly width: 240;
|
|
6
10
|
};
|
|
7
11
|
export declare const FLOW_ZOOM: {
|
|
@@ -12,6 +16,7 @@ export declare const FLOW_ZOOM: {
|
|
|
12
16
|
/** The exit of a node that names none. */
|
|
13
17
|
export declare const DEFAULT_PORT: FlowPort;
|
|
14
18
|
export declare const portsOf: (node: FlowNode) => FlowPort[];
|
|
19
|
+
export declare const nodeHeight: (node: FlowNode) => number;
|
|
15
20
|
/** The exit an edge leaves by: its own, or its node's first. */
|
|
16
21
|
export declare const portOfEdge: (node: FlowNode, edge: FlowEdge) => string;
|
|
17
22
|
export declare const snap: (point: FlowPoint) => FlowPoint;
|
|
@@ -31,6 +36,10 @@ export declare const edgePath: (from: FlowPoint, to: FlowPoint) => string;
|
|
|
31
36
|
export declare const edgeMidpoint: (from: FlowPoint, to: FlowPoint) => FlowPoint;
|
|
32
37
|
/** Where an edge starts and ends, or nothing when one of its ends is gone. */
|
|
33
38
|
export declare function edgeEnds(graph: FlowGraph, edge: FlowEdge): FlowEdgeEnds | undefined;
|
|
39
|
+
/** A run went this way: its node left by the edge's exit, and the node it leads to ran too. */
|
|
40
|
+
export declare function isEdgeTaken(graph: FlowGraph, edge: FlowEdge): boolean;
|
|
41
|
+
/** The position of a node centred on a point: where something dropped there lands. */
|
|
42
|
+
export declare const centeredAt: (point: FlowPoint) => FlowPoint;
|
|
34
43
|
/** The node under a point, other than `except`. */
|
|
35
44
|
export declare const nodeAt: (graph: FlowGraph, point: FlowPoint, except?: string) => FlowNode | undefined;
|
|
36
45
|
export declare const isPortFree: (graph: FlowGraph, node: FlowNode, portId: string) => boolean;
|
package/dist/lib/flow.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
// src/lib/flow.ts
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* A node's box: fixed, so that every anchor and edge is computed without measuring the page. A node with a detail
|
|
4
|
+
* line is taller.
|
|
5
|
+
*/
|
|
3
6
|
var FLOW_NODE = {
|
|
4
7
|
height: 64,
|
|
8
|
+
heightWithDetail: 96,
|
|
5
9
|
width: 240
|
|
6
10
|
};
|
|
7
11
|
var FLOW_ZOOM = {
|
|
@@ -20,6 +24,7 @@ var REVEAL_MARGIN = 48;
|
|
|
20
24
|
var ROW_GAP = 72;
|
|
21
25
|
var COLUMN_GAP = 32;
|
|
22
26
|
var portsOf = (node) => node.ports ?? [DEFAULT_PORT];
|
|
27
|
+
var nodeHeight = (node) => node.detail ? FLOW_NODE.heightWithDetail : FLOW_NODE.height;
|
|
23
28
|
/** The exit an edge leaves by: its own, or its node's first. */
|
|
24
29
|
var portOfEdge = (node, edge) => edge.port ?? portsOf(node)[0]?.id ?? "";
|
|
25
30
|
var nodeById = (graph, id) => graph.nodes.find((node) => node.id === id);
|
|
@@ -36,7 +41,7 @@ function outputAnchor(node, portId) {
|
|
|
36
41
|
if (index < 0) return;
|
|
37
42
|
return {
|
|
38
43
|
x: node.position.x + portX(index, ports.length),
|
|
39
|
-
y: node.position.y +
|
|
44
|
+
y: node.position.y + nodeHeight(node) + PORT_DROP
|
|
40
45
|
};
|
|
41
46
|
}
|
|
42
47
|
var inputAnchor = (node) => ({
|
|
@@ -152,7 +157,18 @@ function edgeEnds(graph, edge) {
|
|
|
152
157
|
tone
|
|
153
158
|
};
|
|
154
159
|
}
|
|
155
|
-
var isInside = (point, node) => point.x >= node.position.x && point.x <= node.position.x + FLOW_NODE.width && point.y >= node.position.y && point.y <= node.position.y +
|
|
160
|
+
var isInside = (point, node) => point.x >= node.position.x && point.x <= node.position.x + FLOW_NODE.width && point.y >= node.position.y && point.y <= node.position.y + nodeHeight(node);
|
|
161
|
+
/** A run went this way: its node left by the edge's exit, and the node it leads to ran too. */
|
|
162
|
+
function isEdgeTaken(graph, edge) {
|
|
163
|
+
const source = nodeById(graph, edge.from);
|
|
164
|
+
const target = nodeById(graph, edge.to);
|
|
165
|
+
return source?.taken !== void 0 && source.taken === portOfEdge(source, edge) && target?.status !== void 0;
|
|
166
|
+
}
|
|
167
|
+
/** The position of a node centred on a point: where something dropped there lands. */
|
|
168
|
+
var centeredAt = (point) => snap({
|
|
169
|
+
x: point.x - FLOW_NODE.width / 2,
|
|
170
|
+
y: point.y - FLOW_NODE.height / 2
|
|
171
|
+
});
|
|
156
172
|
/** The node under a point, other than `except`. */
|
|
157
173
|
var nodeAt = (graph, point, except = "") => graph.nodes.find((node) => node.id !== except && isInside(point, node));
|
|
158
174
|
var isPortFree = (graph, node, portId) => !graph.edges.some((edge) => edge.from === node.id && portOfEdge(node, edge) === portId);
|
|
@@ -188,8 +204,8 @@ var removeEdge = (graph, id) => ({
|
|
|
188
204
|
...graph,
|
|
189
205
|
edges: graph.edges.filter((edge) => edge.id !== id)
|
|
190
206
|
});
|
|
191
|
-
var ROW = FLOW_NODE.height + ROW_GAP;
|
|
192
207
|
var COLUMN = FLOW_NODE.width + COLUMN_GAP;
|
|
208
|
+
var rowOfFlow = (graph) => Math.max(FLOW_NODE.height, ...graph.nodes.map(nodeHeight)) + ROW_GAP;
|
|
193
209
|
/** How far across a node hangs from its parent's exit: the exits share the row out, side by side, around it. */
|
|
194
210
|
function portShift(node, portId) {
|
|
195
211
|
const ports = portsOf(node);
|
|
@@ -204,7 +220,7 @@ function positionAfter(graph, from, portId) {
|
|
|
204
220
|
};
|
|
205
221
|
return snap({
|
|
206
222
|
x: source.position.x + portShift(source, portId),
|
|
207
|
-
y: source.position.y +
|
|
223
|
+
y: source.position.y + nodeHeight(source) + ROW_GAP
|
|
208
224
|
});
|
|
209
225
|
}
|
|
210
226
|
/** The edges that close a loop, found walking from the starts: the layout leaves them out, or a loop has no top. */
|
|
@@ -267,6 +283,7 @@ function layoutFlow(graph) {
|
|
|
267
283
|
const loops = loopEdges(graph);
|
|
268
284
|
const edges = graph.edges.filter((edge) => !loops.has(edge.id) && ids.has(edge.from) && ids.has(edge.to));
|
|
269
285
|
const rows = rowsOf(graph, edges);
|
|
286
|
+
const row = rowOfFlow(graph);
|
|
270
287
|
const placed = /* @__PURE__ */ new Map();
|
|
271
288
|
for (const row of [...new Set(rows.values())].sort((a, b) => a - b)) placeRow(graph.nodes.filter((node) => rows.get(node.id) === row).map((node) => ({
|
|
272
289
|
id: node.id,
|
|
@@ -278,7 +295,7 @@ function layoutFlow(graph) {
|
|
|
278
295
|
...node,
|
|
279
296
|
position: snap({
|
|
280
297
|
x: placed.get(node.id) ?? 0,
|
|
281
|
-
y: (rows.get(node.id) ?? 0) *
|
|
298
|
+
y: (rows.get(node.id) ?? 0) * row
|
|
282
299
|
})
|
|
283
300
|
}))
|
|
284
301
|
};
|
|
@@ -309,7 +326,7 @@ function fitView(nodes, width, height) {
|
|
|
309
326
|
const left = Math.min(...nodes.map((node) => node.position.x));
|
|
310
327
|
const top = Math.min(...nodes.map((node) => node.position.y));
|
|
311
328
|
const right = Math.max(...nodes.map((node) => node.position.x + FLOW_NODE.width));
|
|
312
|
-
const bottom = Math.max(...nodes.map((node) => node.position.y +
|
|
329
|
+
const bottom = Math.max(...nodes.map((node) => node.position.y + nodeHeight(node) + PORT_DROP));
|
|
313
330
|
const zoom = clampZoom(Math.min(1, (width - 96) / (right - left), (height - 96) / (bottom - top)));
|
|
314
331
|
return {
|
|
315
332
|
x: width / 2 - (left + right) / 2 * zoom,
|
|
@@ -329,8 +346,8 @@ function revealIn(viewport, node, width, height) {
|
|
|
329
346
|
const top = viewport.y + node.position.y * zoom;
|
|
330
347
|
return {
|
|
331
348
|
x: viewport.x + shiftInto(left, left + FLOW_NODE.width * zoom, width),
|
|
332
|
-
y: viewport.y + shiftInto(top, top + (
|
|
349
|
+
y: viewport.y + shiftInto(top, top + (nodeHeight(node) + PORT_DROP) * zoom, height),
|
|
333
350
|
zoom
|
|
334
351
|
};
|
|
335
352
|
}
|
|
336
|
-
export { DEFAULT_PORT, FLOW_NODE, FLOW_ZOOM, connect, edgeEnds, edgeMidpoint, edgePath, edgeRoute, fitView, inputAnchor, isPortFree, layoutFlow, moveNode, nodeAt, outputAnchor, portOfEdge, portX, portsOf, positionAfter, removeEdge, removeNode, revealIn, roundedPath, snap, toWorld, zoomAt };
|
|
353
|
+
export { DEFAULT_PORT, FLOW_NODE, FLOW_ZOOM, centeredAt, connect, edgeEnds, edgeMidpoint, edgePath, edgeRoute, fitView, inputAnchor, isEdgeTaken, isPortFree, layoutFlow, moveNode, nodeAt, nodeHeight, outputAnchor, portOfEdge, portX, portsOf, positionAfter, removeEdge, removeNode, revealIn, roundedPath, snap, toWorld, zoomAt };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// src/molecules/jl-accordion-item.css?raw
|
|
2
|
-
var jl_accordion_item_default = ":host {\n display: block;\n border-bottom: 1px solid var(--jl-line);\n /* Lets the content's height animate from 0 to auto. */\n interpolate-size: allow-keywords;\n --pad-x: 0px;\n}\n\nsummary {\n display: flex;\n align-items: center;\n gap: 10px;\n min-height: 48px;\n padding: 12px var(--pad-x);\n border-radius: var(--jl-radius-xs);\n color: var(--jl-text);\n font-size: var(--jl-font-md);\n font-weight: 600;\n line-height: 1.4;\n list-style: none;\n cursor: pointer;\n user-select: none;\n}\n\nsummary::-webkit-details-marker {\n display: none;\n}\n\n.
|
|
2
|
+
var jl_accordion_item_default = ":host {\n display: block;\n border-bottom: 1px solid var(--jl-line);\n /* Lets the content's height animate from 0 to auto. */\n interpolate-size: allow-keywords;\n --pad-x: 0px;\n}\n\nsummary {\n display: flex;\n align-items: center;\n gap: 10px;\n min-height: 48px;\n padding: 12px var(--pad-x);\n border-radius: var(--jl-radius-xs);\n color: var(--jl-text);\n font-size: var(--jl-font-md);\n font-weight: 600;\n line-height: 1.4;\n list-style: none;\n cursor: pointer;\n user-select: none;\n}\n\nsummary::-webkit-details-marker {\n display: none;\n}\n\n.titles {\n display: grid;\n flex: 1;\n min-width: 0;\n}\n\n.description {\n overflow: hidden;\n color: var(--jl-muted);\n font-size: var(--jl-font-sm);\n font-weight: 400;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.icon {\n color: var(--jl-muted);\n}\n\n.chevron {\n color: var(--jl-faint);\n transition: rotate var(--jl-slow) var(--jl-ease);\n}\n\nsummary:hover .chevron {\n color: var(--jl-text);\n}\n\ndetails[open] .chevron {\n rotate: 180deg;\n}\n\nsummary:focus-visible {\n box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--jl-brand) 60%, transparent);\n}\n\n/*\n * The content box is a pseudo-element BASE_STYLES' \"* { box-sizing: inherit }\" does not reach: without this, what\n * it holds (and the slotted fields, which inherit through it) would fall back to content-box.\n */\ndetails::details-content {\n box-sizing: inherit;\n block-size: 0;\n overflow: clip;\n transition:\n block-size var(--jl-slow) var(--jl-ease),\n content-visibility var(--jl-slow) allow-discrete;\n}\n\ndetails[open]::details-content {\n block-size: auto;\n}\n\n.content {\n padding: 0 var(--pad-x) 16px;\n color: var(--jl-text-2);\n font-size: var(--jl-font-md);\n line-height: 1.6;\n}\n\n:host([disabled]) summary {\n color: var(--jl-faint);\n cursor: not-allowed;\n}\n\n:host([disabled]) summary:hover .chevron {\n color: var(--jl-faint);\n}\n";
|
|
3
3
|
export { jl_accordion_item_default as default };
|
|
@@ -12,11 +12,16 @@ export declare class JlAccordionItem extends JlElement {
|
|
|
12
12
|
get(): string;
|
|
13
13
|
set(setter: string | ((prevState: string) => string)): void;
|
|
14
14
|
}>>;
|
|
15
|
+
/** A quieter line under the heading, shown open or closed: what the section holds ("3 fields", "2 pages"). */
|
|
16
|
+
readonly description: Readonly<Readonly<{
|
|
17
|
+
get(): string;
|
|
18
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
19
|
+
}>>;
|
|
15
20
|
readonly open: Readonly<Readonly<{
|
|
16
21
|
get(): boolean;
|
|
17
22
|
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
18
23
|
}>>;
|
|
19
|
-
/** An icon before the heading. */
|
|
24
|
+
/** An icon before the heading; the `icon` slot takes a richer one instead. */
|
|
20
25
|
readonly icon: Readonly<Readonly<{
|
|
21
26
|
get(): string;
|
|
22
27
|
set(setter: string | ((prevState: string) => string)): void;
|
|
@@ -19,8 +19,13 @@ var JlAccordionItem = class extends JlElement {
|
|
|
19
19
|
heading = this.useChange("", (value) => {
|
|
20
20
|
this._refs.heading.textContent = value;
|
|
21
21
|
}, { attribute: "heading" });
|
|
22
|
+
/** A quieter line under the heading, shown open or closed: what the section holds ("3 fields", "2 pages"). */
|
|
23
|
+
description = this.useChange("", (value) => {
|
|
24
|
+
this._refs.description.textContent = value;
|
|
25
|
+
this._refs.description.hidden = value === "";
|
|
26
|
+
}, { attribute: "description" });
|
|
22
27
|
open = this.useChange(false, (value) => this.renderOpen(value), { attribute: "open" });
|
|
23
|
-
/** An icon before the heading. */
|
|
28
|
+
/** An icon before the heading; the `icon` slot takes a richer one instead. */
|
|
24
29
|
icon = this.useChange("", (name) => {
|
|
25
30
|
this._refs.icon.name.set(name);
|
|
26
31
|
this._refs.icon.hidden = name === "";
|
|
@@ -32,6 +37,7 @@ var JlAccordionItem = class extends JlElement {
|
|
|
32
37
|
this._refs.summary.tabIndex = value ? -1 : 0;
|
|
33
38
|
}, { attribute: "disabled" });
|
|
34
39
|
_refs = this.useRefs((ref) => ({
|
|
40
|
+
description: ref(".description"),
|
|
35
41
|
details: ref("details"),
|
|
36
42
|
heading: ref(".heading"),
|
|
37
43
|
icon: ref(".icon"),
|
|
@@ -41,12 +47,21 @@ var JlAccordionItem = class extends JlElement {
|
|
|
41
47
|
return /* @__PURE__ */ jsxs("details", {
|
|
42
48
|
attrs: { part: "base" },
|
|
43
49
|
children: [/* @__PURE__ */ jsxs("summary", { children: [
|
|
44
|
-
/* @__PURE__ */ jsx(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
/* @__PURE__ */ jsx("slot", {
|
|
51
|
+
name: "icon",
|
|
52
|
+
children: /* @__PURE__ */ jsx(JlIcon, {
|
|
53
|
+
class: "icon",
|
|
54
|
+
size: ICON_SIZE,
|
|
55
|
+
hidden: true
|
|
56
|
+
})
|
|
57
|
+
}),
|
|
58
|
+
/* @__PURE__ */ jsxs("span", {
|
|
59
|
+
class: "titles",
|
|
60
|
+
children: [/* @__PURE__ */ jsx("span", { class: "heading" }), /* @__PURE__ */ jsx("span", {
|
|
61
|
+
class: "description",
|
|
62
|
+
hidden: true
|
|
63
|
+
})]
|
|
48
64
|
}),
|
|
49
|
-
/* @__PURE__ */ jsx("span", { class: "heading" }),
|
|
50
65
|
/* @__PURE__ */ jsx(JlIcon, {
|
|
51
66
|
class: "chevron",
|
|
52
67
|
name: "chevron-down",
|
|
@@ -9,6 +9,7 @@ export declare class JlSearch extends JlFormControl {
|
|
|
9
9
|
readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
|
|
10
10
|
input: string;
|
|
11
11
|
search: string;
|
|
12
|
+
enter: string;
|
|
12
13
|
}>>;
|
|
13
14
|
readonly value: Readonly<Readonly<{
|
|
14
15
|
get(): string;
|
|
@@ -52,6 +53,8 @@ export declare class JlSearch extends JlFormControl {
|
|
|
52
53
|
focus(options?: FocusOptions): void;
|
|
53
54
|
/** Selects the whole text, ready to be replaced. */
|
|
54
55
|
select(): void;
|
|
56
|
+
/** Empties the field, as its clear button does: `input` and `search` fire with `''`, and it takes the focus. */
|
|
57
|
+
clear(): void;
|
|
55
58
|
templateHtml(): Node;
|
|
56
59
|
templateCss(): string;
|
|
57
60
|
onConnect(): void;
|
|
@@ -64,7 +67,6 @@ export declare class JlSearch extends JlFormControl {
|
|
|
64
67
|
private onPageKey;
|
|
65
68
|
private scheduleSearch;
|
|
66
69
|
private search;
|
|
67
|
-
private clear;
|
|
68
70
|
private renderValue;
|
|
69
71
|
private renderActions;
|
|
70
72
|
private renderShortcut;
|
|
@@ -77,6 +77,13 @@ var JlSearch = class extends JlFormControl {
|
|
|
77
77
|
select() {
|
|
78
78
|
this._refs.native.select();
|
|
79
79
|
}
|
|
80
|
+
/** Empties the field, as its clear button does: `input` and `search` fire with `''`, and it takes the focus. */
|
|
81
|
+
clear() {
|
|
82
|
+
this.value.set("");
|
|
83
|
+
this.events.emit("input", "");
|
|
84
|
+
this.search("");
|
|
85
|
+
this.focus();
|
|
86
|
+
}
|
|
80
87
|
templateHtml() {
|
|
81
88
|
return /* @__PURE__ */ jsxs("div", {
|
|
82
89
|
class: "control",
|
|
@@ -151,6 +158,7 @@ var JlSearch = class extends JlFormControl {
|
|
|
151
158
|
if (event.key === "Enter") {
|
|
152
159
|
event.preventDefault();
|
|
153
160
|
this.search(this.value.get());
|
|
161
|
+
this.events.emit("enter", this.value.get());
|
|
154
162
|
return;
|
|
155
163
|
}
|
|
156
164
|
if (event.key === "Escape" && this.value.get() !== "") {
|
|
@@ -178,12 +186,6 @@ var JlSearch = class extends JlFormControl {
|
|
|
178
186
|
this._state.lastSearch = query;
|
|
179
187
|
this.events.emit("search", query);
|
|
180
188
|
}
|
|
181
|
-
clear() {
|
|
182
|
-
this.value.set("");
|
|
183
|
-
this.events.emit("input", "");
|
|
184
|
-
this.search("");
|
|
185
|
-
this.focus();
|
|
186
|
-
}
|
|
187
189
|
renderValue(value) {
|
|
188
190
|
const native = this._refs.native;
|
|
189
191
|
if (native.value !== value) native.value = value;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// src/molecules/jl-segmented.css?raw
|
|
2
|
-
var jl_segmented_default = ":host {\n display: inline-flex;\n vertical-align: middle;\n /* Its own width, even as a grid or flex item that would stretch it: block asks for the whole width. */\n justify-self: start;\n max-width: 100%;\n --height: var(--jl-control-md);\n --pad: 12px;\n --font-size: var(--jl-font-sm);\n}\n\n:host([size=\"sm\"]) {\n --height: var(--jl-control-sm);\n --pad: 10px;\n --font-size: var(--jl-font-xs);\n}\n\n:host([size=\"lg\"]) {\n --height: var(--jl-control-lg);\n --pad: 16px;\n --font-size: var(--jl-font-md);\n}\n\n:host([block]) {\n display: flex;\n justify-self: stretch;\n}\n\n.group {\n position: relative;\n display: flex;\n flex: 1;\n overflow-x: auto;\n scrollbar-width: none;\n min-width: 0;\n height: var(--height);\n padding: 2px;\n border: 1px solid var(--jl-line);\n border-radius: var(--jl-radius-sm);\n background: var(--jl-surface-2);\n}\n\n.segment {\n position: relative;\n z-index: 1;\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n gap: 6px;\n min-width: calc(var(--height) - 6px);\n padding: 0 var(--pad);\n border: none;\n border-radius: calc(var(--jl-radius-sm) - 3px);\n background: none;\n color: var(--jl-muted);\n font-size: var(--font-size);\n font-weight: 600;\n white-space: nowrap;\n cursor: pointer;\n transition: color var(--jl-fast) var(--jl-ease);\n}\n\n.segment:hover:not(:disabled) {\n color: var(--jl-text);\n}\n\n.segment[aria-checked=\"true\"] {\n color: var(--jl-text);\n}\n\n.segment:disabled {\n cursor: not-allowed;\n opacity: 0.45;\n}\n\n:host([block]) .segment {\n flex: 1 1 0;\n}\n\n/* The chosen option's raised surface, slid under it. */\n.thumb {\n position: absolute;\n top: 0;\n left: 0;\n width: var(--w);\n height: var(--h);\n border: 1px solid var(--jl-line-strong);\n border-radius: calc(var(--jl-radius-sm) - 3px);\n /* Raised over the track: white on light grey, a lighter grey on a dark ground. */\n background: light-dark(var(--jl-surface), var(--jl-surface-3));\n box-shadow: var(--jl-shadow-sm);\n transform: translate(var(--x), var(--y));\n pointer-events: none;\n}\n\n.thumb[data-placed] {\n transition:\n transform var(--jl-slow) var(--jl-ease),\n width var(--jl-slow) var(--jl-ease);\n}\n\n.group[aria-disabled] {\n opacity: 0.55;\n}\n\n.group[aria-disabled] .segment {\n opacity: 1;\n}\n\n/* The track scrolls when the options do not fit: a ring drawn outside the segment would be cut. */\n.segment:focus-visible {\n box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--jl-brand) 60%, transparent);\n}\n";
|
|
2
|
+
var jl_segmented_default = ":host {\n display: inline-flex;\n vertical-align: middle;\n /* Its own width, even as a grid or flex item that would stretch it: block asks for the whole width. */\n justify-self: start;\n max-width: 100%;\n --height: var(--jl-control-md);\n --pad: 12px;\n --font-size: var(--jl-font-sm);\n}\n\n:host([size=\"sm\"]) {\n --height: var(--jl-control-sm);\n --pad: 10px;\n --font-size: var(--jl-font-xs);\n}\n\n:host([size=\"lg\"]) {\n --height: var(--jl-control-lg);\n --pad: 16px;\n --font-size: var(--jl-font-md);\n}\n\n:host([block]) {\n display: flex;\n justify-self: stretch;\n}\n\n.group {\n position: relative;\n display: flex;\n flex: 1;\n overflow-x: auto;\n scrollbar-width: none;\n min-width: 0;\n height: var(--height);\n padding: 2px;\n border: 1px solid var(--jl-line);\n border-radius: var(--jl-radius-sm);\n background: var(--jl-surface-2);\n}\n\n.segment {\n position: relative;\n z-index: 1;\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n gap: 6px;\n min-width: calc(var(--height) - 6px);\n padding: 0 var(--pad);\n border: none;\n border-radius: calc(var(--jl-radius-sm) - 3px);\n background: none;\n color: var(--jl-muted);\n font-size: var(--font-size);\n font-weight: 600;\n white-space: nowrap;\n cursor: pointer;\n transition: color var(--jl-fast) var(--jl-ease);\n}\n\n.segment:hover:not(:disabled) {\n color: var(--jl-text);\n}\n\n.segment[aria-checked=\"true\"] {\n color: var(--jl-text);\n}\n\n.badge {\n min-width: 18px;\n padding: 0 6px;\n border-radius: var(--jl-radius-pill);\n background: var(--jl-surface-3);\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n font-weight: 650;\n line-height: 18px;\n text-align: center;\n}\n\n.segment[aria-checked=\"true\"] .badge {\n background: var(--jl-accent-soft);\n color: var(--jl-accent);\n}\n\n.segment:disabled {\n cursor: not-allowed;\n opacity: 0.45;\n}\n\n:host([block]) .segment {\n flex: 1 1 0;\n}\n\n/* The chosen option's raised surface, slid under it. */\n.thumb {\n position: absolute;\n top: 0;\n left: 0;\n width: var(--w);\n height: var(--h);\n border: 1px solid var(--jl-line-strong);\n border-radius: calc(var(--jl-radius-sm) - 3px);\n /* Raised over the track: white on light grey, a lighter grey on a dark ground. */\n background: light-dark(var(--jl-surface), var(--jl-surface-3));\n box-shadow: var(--jl-shadow-sm);\n transform: translate(var(--x), var(--y));\n pointer-events: none;\n}\n\n.thumb[data-placed] {\n transition:\n transform var(--jl-slow) var(--jl-ease),\n width var(--jl-slow) var(--jl-ease);\n}\n\n.group[aria-disabled] {\n opacity: 0.55;\n}\n\n.group[aria-disabled] .segment {\n opacity: 1;\n}\n\n/* The track scrolls when the options do not fit: a ring drawn outside the segment would be cut. */\n.segment:focus-visible {\n box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--jl-brand) 60%, transparent);\n}\n";
|
|
3
3
|
export { jl_segmented_default as default };
|
|
@@ -126,13 +126,20 @@ var JlSegmented = class extends JlFormControl {
|
|
|
126
126
|
class: "segment",
|
|
127
127
|
"data-value": option.value,
|
|
128
128
|
"data-disabled": option.disabled === true ? "" : void 0,
|
|
129
|
-
children: [
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
129
|
+
children: [
|
|
130
|
+
option.icon === void 0 ? "" : /* @__PURE__ */ jsx(JlIcon, {
|
|
131
|
+
name: option.icon,
|
|
132
|
+
size: iconSize
|
|
133
|
+
}),
|
|
134
|
+
label === "" ? "" : /* @__PURE__ */ jsx("span", {
|
|
135
|
+
class: "label",
|
|
136
|
+
children: label
|
|
137
|
+
}),
|
|
138
|
+
option.badge === void 0 ? "" : /* @__PURE__ */ jsx("span", {
|
|
139
|
+
class: "badge",
|
|
140
|
+
children: String(option.badge)
|
|
141
|
+
})
|
|
142
|
+
]
|
|
136
143
|
});
|
|
137
144
|
}
|
|
138
145
|
/** The chosen option's index: the one `value` names, or else the first that can be chosen. */
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// src/molecules/jl-stat.css?raw
|
|
2
|
-
var jl_stat_default = ":host {\n display: block;\n --mood: var(--jl-muted);\n}\n\n:host([mood=\"good\"]) {\n --mood: var(--jl-ok);\n}\n\n:host([mood=\"bad\"]) {\n --mood: var(--jl-danger);\n}\n\n.stat {\n display: grid;\n gap: 4px;\n min-width: 0;\n}\n\n.top {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.label {\n flex: 1;\n min-width: 0;\n color: var(--jl-muted);\n font-size: var(--jl-font-sm);\n font-weight: 600;\n line-height: 1.4;\n}\n\n.icon {\n color: var(--jl-faint);\n}\n\n.figure {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n column-gap: 10px;\n}\n\n.value {\n color: var(--jl-text);\n font-size: var(--jl-font-2xl);\n font-variant-numeric: tabular-nums;\n font-weight: 700;\n letter-spacing: -0.02em;\n line-height: 1.2;\n}\n\n.delta {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n color: var(--mood);\n font-size: var(--jl-font-sm);\n font-variant-numeric: tabular-nums;\n font-weight: 650;\n white-space: nowrap;\n}\n\n.caption {\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n line-height: 1.4;\n}\n\n.spark {\n height: 36px;\n margin-top: 8px;\n}\n\n.spark svg {\n display: block;\n width: 100%;\n height: 100%;\n overflow: visible;\n}\n\n.line {\n fill: none;\n stroke: var(--jl-accent);\n stroke-linecap: round;\n stroke-linejoin: round;\n stroke-width: 2;\n vector-effect: non-scaling-stroke;\n}\n\n.area {\n fill: color-mix(in srgb, var(--jl-accent) 10%, transparent);\n stroke: none;\n}\n";
|
|
2
|
+
var jl_stat_default = ":host {\n display: block;\n --mood: var(--jl-muted);\n}\n\n:host([mood=\"good\"]) {\n --mood: var(--jl-ok);\n}\n\n:host([mood=\"bad\"]) {\n --mood: var(--jl-danger);\n}\n\n.stat {\n display: grid;\n gap: 4px;\n min-width: 0;\n}\n\n.top {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.label {\n flex: 1;\n min-width: 0;\n color: var(--jl-muted);\n font-size: var(--jl-font-sm);\n font-weight: 600;\n line-height: 1.4;\n}\n\n.icon {\n color: var(--jl-faint);\n}\n\n:host([tone=\"accent\"]) .icon {\n color: var(--jl-accent);\n}\n\n:host([tone=\"ok\"]) .icon {\n color: var(--jl-ok);\n}\n\n:host([tone=\"warn\"]) .icon {\n color: var(--jl-warn);\n}\n\n:host([tone=\"danger\"]) .icon {\n color: var(--jl-danger);\n}\n\n:host([tone=\"info\"]) .icon {\n color: var(--jl-info);\n}\n\n.figure {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n column-gap: 10px;\n}\n\n.value {\n color: var(--jl-text);\n font-size: var(--jl-font-2xl);\n font-variant-numeric: tabular-nums;\n font-weight: 700;\n letter-spacing: -0.02em;\n line-height: 1.2;\n}\n\n.delta {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n color: var(--mood);\n font-size: var(--jl-font-sm);\n font-variant-numeric: tabular-nums;\n font-weight: 650;\n white-space: nowrap;\n}\n\n.caption {\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n line-height: 1.4;\n}\n\n.spark {\n height: 36px;\n margin-top: 8px;\n}\n\n.spark svg {\n display: block;\n width: 100%;\n height: 100%;\n overflow: visible;\n}\n\n.line {\n fill: none;\n stroke: var(--jl-accent);\n stroke-linecap: round;\n stroke-linejoin: round;\n stroke-width: 2;\n vector-effect: non-scaling-stroke;\n}\n\n.area {\n fill: color-mix(in srgb, var(--jl-accent) 10%, transparent);\n stroke: none;\n}\n";
|
|
3
3
|
export { jl_stat_default as default };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { JlElement } from '../base/jl-element';
|
|
2
2
|
import type { StatTrend } from '../types/jl-stat.types';
|
|
3
|
+
import type { Tone } from '../types/options.types';
|
|
3
4
|
export declare const STAT_TRENDS: readonly StatTrend[];
|
|
4
5
|
/**
|
|
5
6
|
* A key figure: what it measures, its value, how it moved (`delta`, coloured by its trend) and an optional
|
|
@@ -36,6 +37,11 @@ export declare class JlStat extends JlElement {
|
|
|
36
37
|
get(): string;
|
|
37
38
|
set(setter: string | ((prevState: string) => string)): void;
|
|
38
39
|
}>>;
|
|
40
|
+
/** The colour of the icon: neutral (quiet) by default, or what the figure is about (`ok`, `info`…). */
|
|
41
|
+
readonly tone: Readonly<Readonly<{
|
|
42
|
+
get(): Tone;
|
|
43
|
+
set(setter: ((prevState: Tone) => Tone) | Tone): void;
|
|
44
|
+
}>>;
|
|
39
45
|
/** A note under the figure: "vs. last month". */
|
|
40
46
|
readonly caption: Readonly<Readonly<{
|
|
41
47
|
get(): string;
|
|
@@ -2,6 +2,7 @@ import { BASE_STYLES } from "../styles/shared.js";
|
|
|
2
2
|
import { JlElement } from "../base/jl-element.js";
|
|
3
3
|
import { jsonOf, oneOf } from "../lib/attributes.js";
|
|
4
4
|
import { registerIcons } from "../lib/icons.js";
|
|
5
|
+
import { TONES } from "../lib/options.js";
|
|
5
6
|
import { JlIcon } from "../atoms/jl-icon.js";
|
|
6
7
|
import { areaAttribute, pointsAttribute, sparklinePoints } from "../lib/sparkline.js";
|
|
7
8
|
import { trendOfDelta } from "../lib/trend.js";
|
|
@@ -71,6 +72,11 @@ var JlStat = class extends JlElement {
|
|
|
71
72
|
this._refs.icon.name.set(value);
|
|
72
73
|
this._refs.icon.hidden = value === "";
|
|
73
74
|
}, { attribute: "icon" });
|
|
75
|
+
/** The colour of the icon: neutral (quiet) by default, or what the figure is about (`ok`, `info`…). */
|
|
76
|
+
tone = this.useChange("neutral", (value) => this.reflect("tone", value), {
|
|
77
|
+
attribute: "tone",
|
|
78
|
+
parse: oneOf(TONES, "neutral")
|
|
79
|
+
});
|
|
74
80
|
/** A note under the figure: "vs. last month". */
|
|
75
81
|
caption = this.useChange("", (value) => {
|
|
76
82
|
this._refs.caption.textContent = value;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// src/organisms/jl-flow.css?raw
|
|
2
|
-
var jl_flow_default = ":host {\n display: block;\n height: 420px;\n min-height: 0;\n}\n\n.viewport {\n position: relative;\n height: 100%;\n overflow: clip;\n border: 1px solid var(--jl-line);\n border-radius: var(--jl-radius-lg);\n background-color: var(--jl-surface-2);\n background-image: radial-gradient(circle, var(--jl-line-strong) 1px, transparent 1.2px);\n outline: none;\n touch-action: none;\n user-select: none;\n}\n\n.viewport:focus-visible {\n box-shadow: var(--jl-focus-ring);\n}\n\n.world,\n.nodes,\n.edges {\n position: absolute;\n top: 0;\n left: 0;\n}\n\n.world {\n transform-origin: 0 0;\n}\n\n.edges {\n overflow: visible;\n pointer-events: none;\n}\n\n.edge {\n fill: none;\n stroke: color-mix(in srgb, var(--tone, var(--jl-muted)) 60%, var(--jl-line-strong));\n stroke-width: 2;\n stroke-linecap: round;\n}\n\n.edge[data-tone=\"danger\"] {\n stroke-dasharray: 6 5;\n}\n\n.edge-hit {\n fill: none;\n stroke: transparent;\n stroke-width: 14;\n pointer-events: stroke;\n cursor: pointer;\n}\n\n.edge-group:hover .edge {\n stroke: var(--tone, var(--jl-muted));\n}\n\n.edge-group.selected .edge {\n stroke: var(--jl-accent);\n stroke-width: 3;\n}\n\n.draft {\n stroke: var(--jl-accent);\n stroke-dasharray: 5 5;\n}\n\n.node {\n --tone: var(--jl-accent);\n position: absolute;\n top: 0;\n left: 0;\n display: flex;\n
|
|
2
|
+
var jl_flow_default = ":host {\n display: block;\n height: 420px;\n min-height: 0;\n}\n\n.viewport {\n position: relative;\n height: 100%;\n overflow: clip;\n border: 1px solid var(--jl-line);\n border-radius: var(--jl-radius-lg);\n background-color: var(--jl-surface-2);\n background-image: radial-gradient(circle, var(--jl-line-strong) 1px, transparent 1.2px);\n outline: none;\n touch-action: none;\n user-select: none;\n}\n\n.viewport:focus-visible {\n box-shadow: var(--jl-focus-ring);\n}\n\n.world,\n.nodes,\n.edges {\n position: absolute;\n top: 0;\n left: 0;\n}\n\n.world {\n transform-origin: 0 0;\n}\n\n.edges {\n overflow: visible;\n pointer-events: none;\n}\n\n.edge {\n fill: none;\n stroke: color-mix(in srgb, var(--tone, var(--jl-muted)) 60%, var(--jl-line-strong));\n stroke-width: 2;\n stroke-linecap: round;\n}\n\n.edge[data-tone=\"danger\"] {\n stroke-dasharray: 6 5;\n}\n\n.edge-hit {\n fill: none;\n stroke: transparent;\n stroke-width: 14;\n pointer-events: stroke;\n cursor: pointer;\n}\n\n.edge-group:hover .edge {\n stroke: var(--tone, var(--jl-muted));\n}\n\n.edge-group.taken .edge {\n stroke: var(--tone, var(--jl-muted));\n stroke-width: 3;\n}\n\n.edge-group.selected .edge {\n stroke: var(--jl-accent);\n stroke-width: 3;\n}\n\n.draft {\n stroke: var(--jl-accent);\n stroke-dasharray: 5 5;\n}\n\n/* The box's size is the one lib/flow.ts computes the edges with: 240 wide, and the height the script sets (64, or 96\n with a detail line). */\n.node {\n --tone: var(--jl-accent);\n position: absolute;\n top: 0;\n left: 0;\n display: flex;\n flex-direction: column;\n justify-content: center;\n width: 240px;\n height: 64px;\n padding: 0 var(--jl-space-3) var(--jl-space-1);\n border: 1px solid var(--jl-line);\n border-radius: var(--jl-radius-lg);\n background: var(--jl-surface);\n box-shadow: var(--jl-shadow-sm);\n cursor: grab;\n transition:\n border-color var(--jl-fast) var(--jl-ease),\n box-shadow var(--jl-fast) var(--jl-ease);\n}\n\n.node:hover {\n border-color: var(--jl-line-strong);\n}\n\n.node:focus-visible {\n outline: none;\n box-shadow: var(--jl-focus-ring);\n}\n\n/* How a node did in a run: a ring while it works or waits for someone, a border once it is done or failed. */\n.node[data-status=\"running\"] {\n --state: var(--jl-info);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--jl-info) 30%, transparent);\n}\n\n.node[data-status=\"done\"] {\n --state: var(--jl-ok);\n border-color: color-mix(in srgb, var(--jl-ok) 55%, var(--jl-line));\n}\n\n.node[data-status=\"waiting\"] {\n --state: var(--jl-brand);\n border-color: var(--jl-brand);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--jl-brand) 30%, transparent);\n}\n\n.node[data-status=\"failed\"] {\n --state: var(--jl-danger);\n border-color: var(--jl-danger);\n}\n\n.node.selected {\n border-color: var(--jl-accent);\n box-shadow: 0 0 0 3px var(--jl-accent-soft);\n}\n\n.head {\n display: flex;\n align-items: center;\n gap: var(--jl-space-3);\n min-width: 0;\n}\n\n/* With a detail line, the head sits at the top and the detail under it, clear of the exits below. */\n.node:has(.detail) {\n justify-content: flex-start;\n padding-top: 10px;\n}\n\n.detail {\n overflow: hidden;\n margin-top: 6px;\n padding-top: 5px;\n line-height: 16px;\n border-top: 1px dashed var(--jl-line);\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.problem {\n display: inline-flex;\n flex: none;\n color: var(--jl-danger);\n}\n\n.status {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: 50%;\n background: var(--state);\n color: var(--jl-on-accent);\n}\n\n.node[data-status=\"running\"] .status jl-icon {\n animation: spin 1s linear infinite;\n}\n\n@keyframes spin {\n to {\n rotate: 360deg;\n }\n}\n\n.input {\n position: absolute;\n top: -5px;\n left: 50%;\n width: 10px;\n height: 10px;\n border: 2px solid var(--jl-surface);\n border-radius: 50%;\n background: var(--jl-line-strong);\n transform: translateX(-50%);\n}\n\n.icon {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border-radius: var(--jl-radius);\n background: color-mix(in srgb, var(--tone) 14%, var(--jl-surface));\n color: var(--tone);\n}\n\n.text {\n display: grid;\n flex: 1;\n min-width: 0;\n}\n\n.label,\n.description {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.label {\n color: var(--jl-text);\n font-size: var(--jl-font-sm);\n font-weight: 600;\n}\n\n.description {\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n}\n\n.ports {\n position: absolute;\n right: 0;\n bottom: -11px;\n left: 0;\n height: 22px;\n}\n\n.port {\n --tone: var(--jl-muted);\n position: absolute;\n top: 0;\n display: inline-flex;\n align-items: center;\n gap: var(--jl-space-1);\n height: 22px;\n padding: 0 7px;\n border: 1px solid color-mix(in srgb, var(--tone) 35%, var(--jl-line));\n border-radius: var(--jl-radius-pill);\n background: var(--jl-surface);\n color: color-mix(in srgb, var(--tone) 80%, var(--jl-text));\n font-size: var(--jl-font-xs);\n font-weight: 600;\n white-space: nowrap;\n transform: translateX(-50%);\n cursor: crosshair;\n}\n\n.port:has(.add) {\n padding-right: 3px;\n}\n\n/* The exit a run left by. */\n.port[data-taken] {\n border-color: var(--tone);\n background: color-mix(in srgb, var(--tone) 14%, var(--jl-surface));\n}\n\n.dot {\n flex: none;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--tone);\n}\n\n.port-label {\n overflow: hidden;\n min-width: 0;\n text-overflow: ellipsis;\n}\n\n.add,\n.remove-edge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border-radius: 50%;\n cursor: pointer;\n}\n\n.add {\n width: 16px;\n height: 16px;\n border: none;\n background: color-mix(in srgb, var(--tone) 16%, var(--jl-surface));\n color: var(--tone);\n}\n\n.add:hover {\n background: var(--tone);\n color: var(--jl-surface);\n}\n\n.remove-edge {\n position: absolute;\n top: 0;\n left: 0;\n width: 24px;\n height: 24px;\n border: 1px solid var(--jl-line);\n background: var(--jl-surface);\n color: var(--jl-danger);\n}\n\n.remove-edge:hover {\n border-color: var(--jl-danger);\n background: var(--jl-danger);\n color: var(--jl-on-danger);\n}\n\n.empty {\n position: absolute;\n inset: 0;\n display: grid;\n place-items: center;\n padding: var(--jl-space-6);\n color: var(--jl-muted);\n text-align: center;\n pointer-events: none;\n}\n\n.empty[hidden] {\n display: none;\n}\n\n.empty ::slotted(*) {\n pointer-events: auto;\n}\n\n.controls {\n position: absolute;\n bottom: var(--jl-space-3);\n left: var(--jl-space-3);\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 2px;\n border: 1px solid var(--jl-line);\n border-radius: var(--jl-radius);\n background: var(--jl-surface);\n}\n\n/* The zoom buttons: small and quiet, as ghost buttons. */\n.control {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--jl-control-sm);\n height: var(--jl-control-sm);\n padding: 0;\n border: none;\n border-radius: var(--jl-radius-sm);\n background: transparent;\n color: var(--jl-accent);\n cursor: pointer;\n transition: background var(--jl-fast) var(--jl-ease);\n}\n\n.control:hover {\n background: var(--jl-accent-soft);\n}\n\n.zoom-label {\n min-width: 40px;\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n font-variant-numeric: tabular-nums;\n text-align: center;\n}\n\n/* The tones of icons, exits and edges: after .node and .port, whose default tones they replace. */\n[data-tone=\"neutral\"] {\n --tone: var(--jl-muted);\n}\n\n[data-tone=\"accent\"] {\n --tone: var(--jl-accent);\n}\n\n[data-tone=\"ok\"] {\n --tone: var(--jl-ok);\n}\n\n[data-tone=\"warn\"] {\n --tone: var(--jl-warn);\n}\n\n[data-tone=\"danger\"] {\n --tone: var(--jl-danger);\n}\n\n[data-tone=\"info\"] {\n --tone: var(--jl-info);\n}\n\n/* View only: nodes are pressed to be selected, not dragged, and their exits do not connect. */\n:host([readonly]) .node,\n:host([readonly]) .port {\n cursor: pointer;\n}\n\n/* View only: the layout stays as it was given. */\n:host([readonly]) .tidy {\n display: none;\n}\n";
|
|
3
3
|
export { jl_flow_default as default };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { JlElement } from '../base/jl-element';
|
|
2
|
-
import type { FlowAddRequest, FlowGraph } from '../types/jl-flow.types';
|
|
2
|
+
import type { FlowAddRequest, FlowDropRequest, FlowGraph, FlowPoint } from '../types/jl-flow.types';
|
|
3
3
|
/**
|
|
4
4
|
* A flow of nodes joined by edges, to edit: people move the nodes, draw an edge from a node's exit to another node,
|
|
5
5
|
* press + on a free exit to ask for the next node, and move and zoom the view. Every edit goes out as a new graph
|
|
6
|
-
* (`change`); the page adds nodes itself, when `add` asks.
|
|
6
|
+
* (`change`); the page adds nodes itself, when `add` or `drop` asks. Nodes can show how a run went (`status`,
|
|
7
|
+
* `taken`) and what to fix (`problem`).
|
|
7
8
|
*/
|
|
8
9
|
export declare class JlFlow extends JlElement {
|
|
9
10
|
static readonly selector = "jl-flow";
|
|
@@ -11,6 +12,7 @@ export declare class JlFlow extends JlElement {
|
|
|
11
12
|
change: FlowGraph;
|
|
12
13
|
select: string;
|
|
13
14
|
add: FlowAddRequest;
|
|
15
|
+
drop: FlowDropRequest;
|
|
14
16
|
}>>;
|
|
15
17
|
readonly graph: Readonly<Readonly<{
|
|
16
18
|
get(): FlowGraph;
|
|
@@ -26,6 +28,14 @@ export declare class JlFlow extends JlElement {
|
|
|
26
28
|
get(): boolean;
|
|
27
29
|
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
28
30
|
}>>;
|
|
31
|
+
/**
|
|
32
|
+
* The drag-and-drop type the canvas takes (`application/x-step`): something dragged in carrying it asks for a node
|
|
33
|
+
* where it is dropped, with `drop`.
|
|
34
|
+
*/
|
|
35
|
+
readonly dropType: Readonly<Readonly<{
|
|
36
|
+
get(): string;
|
|
37
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
38
|
+
}>>;
|
|
29
39
|
/** The canvas's name for screen readers. */
|
|
30
40
|
readonly label: Readonly<Readonly<{
|
|
31
41
|
get(): string;
|
|
@@ -37,6 +47,12 @@ export declare class JlFlow extends JlElement {
|
|
|
37
47
|
private readonly _refs;
|
|
38
48
|
/** Moves and zooms the view to show every node, never larger than life. */
|
|
39
49
|
fit(): void;
|
|
50
|
+
/** Moves the view, when needed, so that a node is in sight: one just added, or moved with the keys. */
|
|
51
|
+
reveal(id: string): void;
|
|
52
|
+
/** Where a node added after an exit goes, as + puts it: a row below, under that exit. */
|
|
53
|
+
positionAfter(from: string, port: string): FlowPoint;
|
|
54
|
+
/** Where a node added without a place goes: in the middle of the view (its top left corner). */
|
|
55
|
+
centerPosition(): FlowPoint;
|
|
40
56
|
/** Lays the nodes out again, in rows from the starts down (`change` gives the new positions), and shows them all. */
|
|
41
57
|
layout(): void;
|
|
42
58
|
templateHtml(): Node;
|
|
@@ -58,11 +74,13 @@ export declare class JlFlow extends JlElement {
|
|
|
58
74
|
/** An edge dropped on a node joins it; dropped on empty space, it asks for a node there. */
|
|
59
75
|
private connectAt;
|
|
60
76
|
private pressAdd;
|
|
77
|
+
/** Whether a drag carries what the canvas takes: then it may be dropped here. */
|
|
78
|
+
private carriesDrop;
|
|
79
|
+
private dragOver;
|
|
80
|
+
private dropOn;
|
|
61
81
|
private key;
|
|
62
82
|
/** The arrows move the selected node; with Shift, further. */
|
|
63
83
|
private nudge;
|
|
64
|
-
/** Moves the view, when needed, so that a node is in sight: one just added, or moved with the keys. */
|
|
65
|
-
private reveal;
|
|
66
84
|
/** Two fingers or the wheel move the view; with Ctrl or ⌘ (a pinch on a trackpad), they zoom. */
|
|
67
85
|
private wheel;
|
|
68
86
|
private zoomBy;
|
|
@@ -75,11 +93,16 @@ export declare class JlFlow extends JlElement {
|
|
|
75
93
|
private renderNodes;
|
|
76
94
|
private createNode;
|
|
77
95
|
private drawNode;
|
|
96
|
+
/** A colour of the page's own for the icon wins over the tone's. */
|
|
97
|
+
private paintIcon;
|
|
78
98
|
private portElement;
|
|
79
99
|
private place;
|
|
80
100
|
private renderSelection;
|
|
81
101
|
private renderEdges;
|
|
82
|
-
/**
|
|
102
|
+
/**
|
|
103
|
+
* An edge, with a wide invisible twin that catches the pointer: a thin line is still easy to press. One a run took
|
|
104
|
+
* stands out.
|
|
105
|
+
*/
|
|
83
106
|
private edgeGroup;
|
|
84
107
|
/** The button that removes the selected edge, in its middle. */
|
|
85
108
|
private renderEdgeTool;
|
|
@@ -4,13 +4,17 @@ import { jsonOf } from "../lib/attributes.js";
|
|
|
4
4
|
import { registerIcons } from "../lib/icons.js";
|
|
5
5
|
import { setOrRemove, svgElement } from "../lib/dom.js";
|
|
6
6
|
import { JlIcon } from "../atoms/jl-icon.js";
|
|
7
|
-
import { FLOW_NODE, FLOW_ZOOM, connect, edgeEnds, edgeMidpoint, edgePath, fitView, isPortFree, layoutFlow, moveNode, nodeAt, outputAnchor, portX, portsOf, positionAfter, removeEdge, removeNode, revealIn, snap, toWorld, zoomAt } from "../lib/flow.js";
|
|
7
|
+
import { FLOW_NODE, FLOW_ZOOM, centeredAt, connect, edgeEnds, edgeMidpoint, edgePath, fitView, isEdgeTaken, isPortFree, layoutFlow, moveNode, nodeAt, nodeHeight, outputAnchor, portX, portsOf, positionAfter, removeEdge, removeNode, revealIn, snap, toWorld, zoomAt } from "../lib/flow.js";
|
|
8
8
|
import jl_flow_default from "./jl-flow.css_raw.js";
|
|
9
9
|
import { css } from "@jadis/core";
|
|
10
|
-
import { Maximize, Minus, Plus, WandSparkles, X } from "lucide";
|
|
10
|
+
import { Check, CircleAlert, Hourglass, LoaderCircle, Maximize, Minus, Plus, WandSparkles, X } from "lucide";
|
|
11
11
|
import { Fragment, jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
12
12
|
// src/organisms/jl-flow.tsx
|
|
13
13
|
registerIcons({
|
|
14
|
+
check: Check,
|
|
15
|
+
"circle-alert": CircleAlert,
|
|
16
|
+
hourglass: Hourglass,
|
|
17
|
+
"loader-circle": LoaderCircle,
|
|
14
18
|
maximize: Maximize,
|
|
15
19
|
minus: Minus,
|
|
16
20
|
plus: Plus,
|
|
@@ -32,6 +36,7 @@ var PERCENT = 100;
|
|
|
32
36
|
var ICON_SIZE = 16;
|
|
33
37
|
var ADD_ICON_SIZE = 12;
|
|
34
38
|
var CONTROL_ICON_SIZE = 14;
|
|
39
|
+
var STATUS_ICON_SIZE = 12;
|
|
35
40
|
var PORT_GAP = 6;
|
|
36
41
|
var ID_LENGTH = 8;
|
|
37
42
|
var DELETE_KEYS = /* @__PURE__ */ new Set(["Delete", "Backspace"]);
|
|
@@ -53,6 +58,20 @@ var ARROWS = /* @__PURE__ */ new Map([
|
|
|
53
58
|
y: -1
|
|
54
59
|
}]
|
|
55
60
|
]);
|
|
61
|
+
var STATUS_ICONS = {
|
|
62
|
+
done: "check",
|
|
63
|
+
failed: "x",
|
|
64
|
+
running: "loader-circle",
|
|
65
|
+
waiting: "hourglass"
|
|
66
|
+
};
|
|
67
|
+
var STATUS_LABELS = {
|
|
68
|
+
done: "Done",
|
|
69
|
+
failed: "Failed",
|
|
70
|
+
running: "Running",
|
|
71
|
+
waiting: "Waiting"
|
|
72
|
+
};
|
|
73
|
+
/** What screen readers hear after a node's label: how it did in the run, and what to fix. */
|
|
74
|
+
var noteOf = (node) => [node.status ? STATUS_LABELS[node.status] : "", node.problem ?? ""].filter((part) => part !== "").join(". ");
|
|
56
75
|
var markOf = (element, name) => element?.closest(`[${name}]`)?.getAttribute(name) ?? void 0;
|
|
57
76
|
var isControl = (event) => event.composedPath().some((element) => element instanceof Element && element.hasAttribute("data-control"));
|
|
58
77
|
function targetOf(event) {
|
|
@@ -71,7 +90,8 @@ var addLabel = (node, port) => port.label ? `Add after ${node.label}, ${port.lab
|
|
|
71
90
|
/**
|
|
72
91
|
* A flow of nodes joined by edges, to edit: people move the nodes, draw an edge from a node's exit to another node,
|
|
73
92
|
* press + on a free exit to ask for the next node, and move and zoom the view. Every edit goes out as a new graph
|
|
74
|
-
* (`change`); the page adds nodes itself, when `add` asks.
|
|
93
|
+
* (`change`); the page adds nodes itself, when `add` or `drop` asks. Nodes can show how a run went (`status`,
|
|
94
|
+
* `taken`) and what to fix (`problem`).
|
|
75
95
|
*/
|
|
76
96
|
var JlFlow = class extends JlElement {
|
|
77
97
|
static selector = "jl-flow";
|
|
@@ -90,6 +110,11 @@ var JlFlow = class extends JlElement {
|
|
|
90
110
|
this.reflect("readonly", value);
|
|
91
111
|
this.render();
|
|
92
112
|
}, { attribute: "readonly" });
|
|
113
|
+
/**
|
|
114
|
+
* The drag-and-drop type the canvas takes (`application/x-step`): something dragged in carrying it asks for a node
|
|
115
|
+
* where it is dropped, with `drop`.
|
|
116
|
+
*/
|
|
117
|
+
dropType = this.useChange("", () => void 0, { attribute: "drop-type" });
|
|
93
118
|
/** The canvas's name for screen readers. */
|
|
94
119
|
label = this.useChange("", (value) => this._refs.viewport.setAttribute("aria-label", value || DEFAULT_LABEL), { attribute: "label" });
|
|
95
120
|
_state = {
|
|
@@ -133,6 +158,27 @@ var JlFlow = class extends JlElement {
|
|
|
133
158
|
this.setViewport(fitView(this._state.graph.nodes, width, height));
|
|
134
159
|
});
|
|
135
160
|
}
|
|
161
|
+
/** Moves the view, when needed, so that a node is in sight: one just added, or moved with the keys. */
|
|
162
|
+
reveal(id) {
|
|
163
|
+
this.whenRendered(() => {
|
|
164
|
+
const node = this.nodeOf(id);
|
|
165
|
+
const { width, height } = this._refs.viewport.getBoundingClientRect();
|
|
166
|
+
if (!node || width === 0) return;
|
|
167
|
+
this.setViewport(revealIn(this._state.viewport, node, width, height));
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/** Where a node added after an exit goes, as + puts it: a row below, under that exit. */
|
|
171
|
+
positionAfter(from, port) {
|
|
172
|
+
return positionAfter(this._state.graph, from, port);
|
|
173
|
+
}
|
|
174
|
+
/** Where a node added without a place goes: in the middle of the view (its top left corner). */
|
|
175
|
+
centerPosition() {
|
|
176
|
+
const { width, height } = this._refs.viewport.getBoundingClientRect();
|
|
177
|
+
return centeredAt(toWorld(this._state.viewport, {
|
|
178
|
+
x: width / 2,
|
|
179
|
+
y: height / 2
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
136
182
|
/** Lays the nodes out again, in rows from the starts down (`change` gives the new positions), and shows them all. */
|
|
137
183
|
layout() {
|
|
138
184
|
this.whenRendered(() => {
|
|
@@ -236,6 +282,8 @@ var JlFlow = class extends JlElement {
|
|
|
236
282
|
this.on(viewport, "wheel", (event) => this.wheel(event));
|
|
237
283
|
this.on(viewport, "keydown", (event) => this.key(event));
|
|
238
284
|
this.on(viewport, "click", (event) => this.pressAdd(event));
|
|
285
|
+
this.on(viewport, "dragover", (event) => this.dragOver(event));
|
|
286
|
+
this.on(viewport, "drop", (event) => this.dropOn(event));
|
|
239
287
|
this.on(viewport, "focusin", (event) => {
|
|
240
288
|
const node = targetOf(event).node;
|
|
241
289
|
if (node !== void 0) this.select(node);
|
|
@@ -424,6 +472,24 @@ var JlFlow = class extends JlElement {
|
|
|
424
472
|
position: positionAfter(this._state.graph, node, add)
|
|
425
473
|
});
|
|
426
474
|
}
|
|
475
|
+
/** Whether a drag carries what the canvas takes: then it may be dropped here. */
|
|
476
|
+
carriesDrop(event) {
|
|
477
|
+
const type = this.dropType.get();
|
|
478
|
+
return type !== "" && !this.readonly.get() && (event.dataTransfer?.types.includes(type) ?? false);
|
|
479
|
+
}
|
|
480
|
+
dragOver(event) {
|
|
481
|
+
if (this.carriesDrop(event)) event.preventDefault();
|
|
482
|
+
}
|
|
483
|
+
dropOn(event) {
|
|
484
|
+
if (!this.carriesDrop(event)) return;
|
|
485
|
+
event.preventDefault();
|
|
486
|
+
event.stopPropagation();
|
|
487
|
+
const data = event.dataTransfer?.getData(this.dropType.get()) ?? "";
|
|
488
|
+
this.events.emit("drop", {
|
|
489
|
+
data,
|
|
490
|
+
position: centeredAt(this.worldPoint(event))
|
|
491
|
+
});
|
|
492
|
+
}
|
|
427
493
|
key(event) {
|
|
428
494
|
const target = targetOf(event);
|
|
429
495
|
if (target.control || target.add !== void 0) return;
|
|
@@ -453,13 +519,6 @@ var JlFlow = class extends JlElement {
|
|
|
453
519
|
this.commit(moveNode(this._state.graph, node.id, position));
|
|
454
520
|
this.reveal(node.id);
|
|
455
521
|
}
|
|
456
|
-
/** Moves the view, when needed, so that a node is in sight: one just added, or moved with the keys. */
|
|
457
|
-
reveal(id) {
|
|
458
|
-
const node = this.nodeOf(id);
|
|
459
|
-
const { width, height } = this._refs.viewport.getBoundingClientRect();
|
|
460
|
-
if (!node || width === 0) return;
|
|
461
|
-
this.setViewport(revealIn(this._state.viewport, node, width, height));
|
|
462
|
-
}
|
|
463
522
|
/** Two fingers or the wheel move the view; with Ctrl or ⌘ (a pinch on a trackpad), they zoom. */
|
|
464
523
|
wheel(event) {
|
|
465
524
|
event.preventDefault();
|
|
@@ -535,30 +594,63 @@ var JlFlow = class extends JlElement {
|
|
|
535
594
|
drawNode(element, node) {
|
|
536
595
|
const ports = portsOf(node);
|
|
537
596
|
element.setAttribute("aria-label", node.label);
|
|
597
|
+
setOrRemove(element, "aria-description", noteOf(node));
|
|
538
598
|
element.part.toggle("start", node.start === true);
|
|
539
599
|
setOrRemove(element, "data-tone", node.tone ?? "");
|
|
600
|
+
setOrRemove(element, "data-status", node.status ?? "");
|
|
601
|
+
element.style.height = `${nodeHeight(node)}px`;
|
|
602
|
+
this.paintIcon(element, node.color ?? "");
|
|
540
603
|
element.replaceChildren(/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
541
604
|
node.start === true ? null : /* @__PURE__ */ jsx("span", { class: "input" }),
|
|
542
|
-
node.icon ? /* @__PURE__ */ jsx("span", {
|
|
543
|
-
class: "icon",
|
|
544
|
-
attrs: { part: "icon" },
|
|
545
|
-
children: /* @__PURE__ */ jsx(JlIcon, {
|
|
546
|
-
name: node.icon,
|
|
547
|
-
size: ICON_SIZE
|
|
548
|
-
})
|
|
549
|
-
}) : null,
|
|
550
605
|
/* @__PURE__ */ jsxs("span", {
|
|
551
|
-
class: "
|
|
552
|
-
children: [
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
606
|
+
class: "head",
|
|
607
|
+
children: [
|
|
608
|
+
node.icon ? /* @__PURE__ */ jsx("span", {
|
|
609
|
+
class: "icon",
|
|
610
|
+
attrs: { part: "icon" },
|
|
611
|
+
children: /* @__PURE__ */ jsx(JlIcon, {
|
|
612
|
+
name: node.icon,
|
|
613
|
+
size: ICON_SIZE
|
|
614
|
+
})
|
|
615
|
+
}) : null,
|
|
616
|
+
/* @__PURE__ */ jsxs("span", {
|
|
617
|
+
class: "text",
|
|
618
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
619
|
+
class: "label",
|
|
620
|
+
attrs: { part: "label" },
|
|
621
|
+
children: node.label
|
|
622
|
+
}), node.description ? /* @__PURE__ */ jsx("span", {
|
|
623
|
+
class: "description",
|
|
624
|
+
attrs: { part: "description" },
|
|
625
|
+
children: node.description
|
|
626
|
+
}) : null]
|
|
627
|
+
}),
|
|
628
|
+
node.problem ? /* @__PURE__ */ jsx("span", {
|
|
629
|
+
class: "problem",
|
|
630
|
+
title: node.problem,
|
|
631
|
+
attrs: { part: "problem" },
|
|
632
|
+
children: /* @__PURE__ */ jsx(JlIcon, {
|
|
633
|
+
name: "circle-alert",
|
|
634
|
+
size: ICON_SIZE
|
|
635
|
+
})
|
|
636
|
+
}) : null,
|
|
637
|
+
node.status ? /* @__PURE__ */ jsx("span", {
|
|
638
|
+
class: "status",
|
|
639
|
+
title: STATUS_LABELS[node.status],
|
|
640
|
+
attrs: { part: "status" },
|
|
641
|
+
children: /* @__PURE__ */ jsx(JlIcon, {
|
|
642
|
+
name: STATUS_ICONS[node.status],
|
|
643
|
+
size: STATUS_ICON_SIZE
|
|
644
|
+
})
|
|
645
|
+
}) : null
|
|
646
|
+
]
|
|
561
647
|
}),
|
|
648
|
+
node.detail ? /* @__PURE__ */ jsx("span", {
|
|
649
|
+
class: "detail",
|
|
650
|
+
title: node.detail,
|
|
651
|
+
attrs: { part: "detail" },
|
|
652
|
+
children: node.detail
|
|
653
|
+
}) : null,
|
|
562
654
|
/* @__PURE__ */ jsx("span", {
|
|
563
655
|
class: "ports",
|
|
564
656
|
children: ports.map((port, index) => this.portElement(node, port, index, ports.length))
|
|
@@ -566,6 +658,14 @@ var JlFlow = class extends JlElement {
|
|
|
566
658
|
] }));
|
|
567
659
|
this.place(element, node.position);
|
|
568
660
|
}
|
|
661
|
+
/** A colour of the page's own for the icon wins over the tone's. */
|
|
662
|
+
paintIcon(element, color) {
|
|
663
|
+
if (color === "") {
|
|
664
|
+
element.style.removeProperty("--tone");
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
element.style.setProperty("--tone", color);
|
|
668
|
+
}
|
|
569
669
|
portElement(node, port, index, count) {
|
|
570
670
|
const free = !this.readonly.get() && isPortFree(this._state.graph, node, port.id);
|
|
571
671
|
const element = /* @__PURE__ */ jsxs("span", {
|
|
@@ -594,6 +694,7 @@ var JlFlow = class extends JlElement {
|
|
|
594
694
|
element.style.left = `${portX(index, count)}px`;
|
|
595
695
|
element.style.maxWidth = `${FLOW_NODE.width / count - PORT_GAP}px`;
|
|
596
696
|
setOrRemove(element, "data-tone", port.tone ?? "");
|
|
697
|
+
element.toggleAttribute("data-taken", node.taken === port.id);
|
|
597
698
|
return element;
|
|
598
699
|
}
|
|
599
700
|
place(element, position) {
|
|
@@ -611,16 +712,20 @@ var JlFlow = class extends JlElement {
|
|
|
611
712
|
const { edge: selected, graph } = this._state;
|
|
612
713
|
const groups = graph.edges.flatMap((edge) => {
|
|
613
714
|
const ends = edgeEnds(graph, edge);
|
|
614
|
-
return ends ? [this.edgeGroup(edge.id, ends, edge.id === selected)] : [];
|
|
715
|
+
return ends ? [this.edgeGroup(edge.id, ends, edge.id === selected, isEdgeTaken(graph, edge))] : [];
|
|
615
716
|
});
|
|
616
717
|
this._svg.root.replaceChildren(...groups, this._svg.draft);
|
|
617
718
|
this.renderEdgeTool();
|
|
618
719
|
}
|
|
619
|
-
/**
|
|
620
|
-
|
|
720
|
+
/**
|
|
721
|
+
* An edge, with a wide invisible twin that catches the pointer: a thin line is still easy to press. One a run took
|
|
722
|
+
* stands out.
|
|
723
|
+
*/
|
|
724
|
+
edgeGroup(id, ends, selected, taken) {
|
|
621
725
|
const d = edgePath(ends.from, ends.to);
|
|
726
|
+
const states = [selected ? "selected" : "", taken ? "taken" : ""].filter((state) => state !== "");
|
|
622
727
|
const group = svgElement("g", {
|
|
623
|
-
class:
|
|
728
|
+
class: ["edge-group", ...states].join(" "),
|
|
624
729
|
"data-edge": id
|
|
625
730
|
});
|
|
626
731
|
group.append(svgElement("path", {
|
|
@@ -630,7 +735,7 @@ var JlFlow = class extends JlElement {
|
|
|
630
735
|
class: "edge",
|
|
631
736
|
d,
|
|
632
737
|
"data-tone": ends.tone,
|
|
633
|
-
part:
|
|
738
|
+
part: ["edge", ...states].join(" ")
|
|
634
739
|
}));
|
|
635
740
|
return group;
|
|
636
741
|
}
|
|
@@ -10,6 +10,8 @@ export type FlowPort = {
|
|
|
10
10
|
label?: string;
|
|
11
11
|
tone?: Tone;
|
|
12
12
|
};
|
|
13
|
+
/** How a node did in a run, shown on it: at work, finished, waiting for someone, or failed. */
|
|
14
|
+
export type FlowNodeStatus = 'running' | 'done' | 'waiting' | 'failed';
|
|
13
15
|
export type FlowNode = {
|
|
14
16
|
id: string;
|
|
15
17
|
label: string;
|
|
@@ -17,9 +19,19 @@ export type FlowNode = {
|
|
|
17
19
|
position: FlowPoint;
|
|
18
20
|
/** A second, quieter line under the label. */
|
|
19
21
|
description?: string;
|
|
22
|
+
/** A third line, under a dashed rule: what the node is set to do. The node is taller with it. */
|
|
23
|
+
detail?: string;
|
|
20
24
|
icon?: string;
|
|
21
25
|
/** The colour of the icon: `accent` by default. */
|
|
22
26
|
tone?: Tone;
|
|
27
|
+
/** The icon's colour as any CSS colour, for kinds the tones do not cover (`var(--my-ai)`); wins over `tone`. */
|
|
28
|
+
color?: string;
|
|
29
|
+
/** How it did in a run. */
|
|
30
|
+
status?: FlowNodeStatus;
|
|
31
|
+
/** The exit a run left it by: that exit, and its edges to nodes with a status, stand out. */
|
|
32
|
+
taken?: string;
|
|
33
|
+
/** Something to fix before the flow can run: a mark on the node, with this text as its explanation. */
|
|
34
|
+
problem?: string;
|
|
23
35
|
/** The exits. Absent, the node has one without a name; empty, it has none (it ends the flow). */
|
|
24
36
|
ports?: FlowPort[];
|
|
25
37
|
/** A start takes no incoming edge. */
|
|
@@ -42,6 +54,11 @@ export type FlowAddRequest = {
|
|
|
42
54
|
port: string;
|
|
43
55
|
position: FlowPoint;
|
|
44
56
|
};
|
|
57
|
+
/** Asks for a node where something was dropped: what it carried, and where to put the node. */
|
|
58
|
+
export type FlowDropRequest = {
|
|
59
|
+
data: string;
|
|
60
|
+
position: FlowPoint;
|
|
61
|
+
};
|
|
45
62
|
/** How far the view is moved, and how much it is zoomed. */
|
|
46
63
|
export type FlowViewport = {
|
|
47
64
|
x: number;
|