@peter.prib/renderengine 0.4.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/REFERENCE.md +267 -17
- package/dist/renderengine-workspace.umd.js +5289 -2895
- package/dist/renderengine.umd.js +4243 -2120
- package/examples/archimate.html +8 -28
- package/examples/data-model.html +45 -556
- package/package.json +70 -70
- package/src/archimateArchiFormat.js +11 -3
- package/src/archimateExchangeFormat.js +17 -9
- package/src/charting/ArchimateDiagramEditor.js +1 -1
- package/src/charting/Axis.js +20 -0
- package/src/charting/DiagramEditor.js +35 -3
- package/src/charting/Dialog.js +195 -0
- package/src/charting/DuplicatesDialog.js +108 -0
- package/src/charting/FormDialog.js +28 -97
- package/src/charting/InteractionManager.js +3 -0
- package/src/charting/Legend.js +45 -2
- package/src/charting/MergeCompareDialog.js +81 -0
- package/src/charting/PopupMenu.js +12 -3
- package/src/charting/SettingsDialog.js +6 -2
- package/src/charting/SettingsManager.js +14 -1
- package/src/charting/archimate/ArchimateCanvasEditor.js +190 -15
- package/src/charting/archimate/ArchimatePalette.js +1 -1
- package/src/charting/charts/AreaChart.js +5 -2
- package/src/charting/charts/BarChart.js +5 -3
- package/src/charting/charts/BarChart3DOrbit.js +5 -7
- package/src/charting/charts/BubbleChart.js +5 -7
- package/src/charting/charts/BubbleChart3D.js +5 -7
- package/src/charting/charts/CandlestickChart.js +3 -1
- package/src/charting/charts/DensityChart.js +4 -6
- package/src/charting/charts/GroupedBarChart.js +2 -2
- package/src/charting/charts/LineChart.js +5 -2
- package/src/charting/charts/ParallelCoordinatesChart.js +2 -2
- package/src/charting/charts/PipeChart.js +4 -6
- package/src/charting/charts/RadarChart.js +5 -6
- package/src/charting/charts/ScatterChart.js +5 -7
- package/src/charting/charts/StackedBarChart.js +4 -2
- package/src/charting/charts/StackedBarChart3DOrbit.js +5 -7
- package/src/charting/charts/StreamGraphChart.js +5 -2
- package/src/charting/charts/WaterfallChart.js +2 -1
- package/src/charting/content/ContentSource.js +129 -0
- package/src/charting/content/ContentSources.js +77 -0
- package/src/charting/datamodel/DataModel.js +116 -0
- package/src/charting/diagrams/ArchimateModel.js +38 -3
- package/src/charting/diagrams/ArchimateModelChecker.js +13 -4
- package/src/charting/diagrams/MergeElementsCommand.js +124 -101
- package/src/charting/diagrams/SvgZoom.js +20 -1
- package/src/charting/diagrams/duplicates.js +95 -13
- package/src/charting/duplicateSearchForm.js +58 -0
- package/src/charting/modelTree/ModelTree.js +239 -202
- package/src/charting/modelTree/ModelTreePanel.js +955 -919
- package/src/charting/modelTree/TreeBuilder.js +93 -45
- package/src/charting/modelTree/TreeViewMode.js +9 -6
- package/src/charting/modelTree/viewFolders.js +15 -6
- package/src/charting/requirements/AzureDevOpsApi.js +55 -0
- package/src/charting/requirements/GitLabApi.js +43 -0
- package/src/charting/requirements/JiraApi.js +69 -0
- package/src/charting/requirements/TrackerApi.js +142 -0
- package/src/charting/requirements/TrackerExport.js +3 -2
- package/src/charting/requirements/index.js +5 -0
- package/src/charting/requirements/trackerApis.js +21 -0
- package/src/icons/IconSet.js +86 -0
- package/src/icons/index.js +59 -0
- package/src/index.js +67 -10
- package/src/workspace/dom.js +56 -42
- package/src/workspace/index.js +1 -1
- package/src/workspace/nav/NavEntry.js +42 -0
- package/src/workspace/nav/NavGroup.js +43 -0
- package/src/workspace/nav/NavItem.js +53 -0
- package/src/workspace/nav/NavMenu.js +143 -0
- package/src/workspace/nav/NavSection.js +17 -0
- package/src/workspace/nav/index.js +5 -0
- package/src/workspace/parts/ArchimateCanvasPart.js +8 -3
- package/src/workspace/parts/DataModelPart.js +91 -0
- package/src/workspace/parts/EditorPart.js +8 -0
- package/src/workspace/parts/ModelHosted.js +1 -0
- package/src/workspace/parts/ModelsPart.js +765 -622
- package/src/workspace/styles.js +28 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IconSet — named icons as data, built as `<svg>` specs for el().
|
|
3
|
+
*
|
|
4
|
+
* An icon is JSON: a viewBox, how it is painted, and its shapes as
|
|
5
|
+
* `[tag, attrs]` pairs:
|
|
6
|
+
*
|
|
7
|
+
* { viewBox: '0 0 24 24', paint: 'stroke', shapes: [['circle', { cx: 12, cy: 12, r: 10 }]] }
|
|
8
|
+
*
|
|
9
|
+
* `paint` is 'stroke' (outlined in currentColor — the default, so an icon
|
|
10
|
+
* takes its parent's colour), 'fill' (filled in currentColor) or 'none' (the
|
|
11
|
+
* shapes carry their own colours: a brand mark). No markup is parsed: el()
|
|
12
|
+
* builds the spec in the SVG namespace.
|
|
13
|
+
*/
|
|
14
|
+
const PAINT = {
|
|
15
|
+
stroke: { fill: 'none', stroke: 'currentColor', 'stroke-width': 2, 'stroke-linecap': 'round', 'stroke-linejoin': 'round' },
|
|
16
|
+
fill: { fill: 'currentColor' },
|
|
17
|
+
none: {},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class IconSet {
|
|
21
|
+
/** @param {Object<string, Object>} [defs] - Name → icon */
|
|
22
|
+
constructor(defs = {}) {
|
|
23
|
+
this.defs = new Map();
|
|
24
|
+
this.add(defs);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Add (or replace) icons, name → icon. */
|
|
28
|
+
add(defs) {
|
|
29
|
+
for (const [name, def] of Object.entries(defs)) this.define(name, def);
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Add (or replace) one icon; throws when it is not one. */
|
|
34
|
+
define(name, def) {
|
|
35
|
+
if (typeof def?.viewBox !== 'string' || !Array.isArray(def.shapes) || !def.shapes.length) {
|
|
36
|
+
throw new Error(`IconSet: icon "${name}" needs a viewBox and shapes`);
|
|
37
|
+
}
|
|
38
|
+
if (def.paint != null && !(def.paint in PAINT)) {
|
|
39
|
+
throw new Error(`IconSet: icon "${name}" paint is ${Object.keys(PAINT).join(', ')}, not "${def.paint}"`);
|
|
40
|
+
}
|
|
41
|
+
for (const shape of def.shapes) {
|
|
42
|
+
if (!Array.isArray(shape) || typeof shape[0] !== 'string') throw new Error(`IconSet: icon "${name}" shapes are [tag, attrs] pairs`);
|
|
43
|
+
}
|
|
44
|
+
this.defs.set(name, def);
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
has(name) { return this.defs.has(name); }
|
|
49
|
+
|
|
50
|
+
get(name) { return this.defs.get(name) ?? null; }
|
|
51
|
+
|
|
52
|
+
/** Every icon's name, in the order added. */
|
|
53
|
+
names() { return [...this.defs.keys()]; }
|
|
54
|
+
|
|
55
|
+
/** A copy of this set with `defs` added: a host's own icons over these. */
|
|
56
|
+
with(defs) {
|
|
57
|
+
return new IconSet(Object.fromEntries(this.defs)).add(defs);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The icon as an el() spec.
|
|
62
|
+
* @param {string} name
|
|
63
|
+
* @param {Object} [options]
|
|
64
|
+
* @param {number|string} [options.size] - Width and height (default 24; CSS can override)
|
|
65
|
+
* @param {string} [options.title] - Its accessible name; without one it is hidden from screen readers
|
|
66
|
+
* @param {string|string[]} [options.cls] - Extra class names
|
|
67
|
+
*/
|
|
68
|
+
spec(name, { size = 24, title = null, cls = null } = {}) {
|
|
69
|
+
const def = this.defs.get(name);
|
|
70
|
+
if (!def) throw new Error(`IconSet: no icon "${name}"`);
|
|
71
|
+
return {
|
|
72
|
+
tag: 'svg',
|
|
73
|
+
cls: ['re-icon', ...(Array.isArray(cls) ? cls : [cls])],
|
|
74
|
+
attrs: {
|
|
75
|
+
viewBox: def.viewBox, width: size, height: size, focusable: 'false',
|
|
76
|
+
role: title ? 'img' : null, 'aria-hidden': title ? null : 'true',
|
|
77
|
+
...PAINT[def.paint ?? 'stroke'],
|
|
78
|
+
},
|
|
79
|
+
dataset: { icon: name },
|
|
80
|
+
children: [
|
|
81
|
+
...(title ? [{ tag: 'title', text: title }] : []),
|
|
82
|
+
...def.shapes.map(([tag, attrs]) => ({ tag, attrs })),
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The built-in icons — a shared pool any host draws from (`ICONS.spec('cpu')`,
|
|
3
|
+
* or `icon('cpu')`), extended with its own (`ICONS.with({ ... })`). Outlined
|
|
4
|
+
* icons on a 24-unit grid, stroked in currentColor; the brand marks keep their
|
|
5
|
+
* colours. Names say what the picture is, not where it is used.
|
|
6
|
+
*/
|
|
7
|
+
import { IconSet } from './IconSet.js';
|
|
8
|
+
|
|
9
|
+
const box = (x, y, width, height, rx = 1) => ['rect', { x, y, width, height, rx }];
|
|
10
|
+
const path = (d) => ['path', { d }];
|
|
11
|
+
|
|
12
|
+
export const ICON_DEFS = {
|
|
13
|
+
dashboard: { viewBox: '0 0 24 24', shapes: [box(3, 3, 7, 9), box(14, 3, 7, 5), box(14, 12, 7, 9), box(3, 16, 7, 5)] },
|
|
14
|
+
zap: { viewBox: '0 0 24 24', shapes: [path('m13 2-2 10h8L11 22l2-10H5L13 2z')] },
|
|
15
|
+
clock: { viewBox: '0 0 24 24', shapes: [['circle', { cx: 12, cy: 12, r: 10 }], ['polyline', { points: '12 6 12 12 16 14' }]] },
|
|
16
|
+
'clipboard-list': { viewBox: '0 0 24 24', shapes: [
|
|
17
|
+
box(8, 2, 8, 4),
|
|
18
|
+
path('M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'),
|
|
19
|
+
path('M12 11h4'), path('M12 16h4'), path('M8 11h.01'), path('M8 16h.01'),
|
|
20
|
+
] },
|
|
21
|
+
book: { viewBox: '0 0 24 24', shapes: [
|
|
22
|
+
path('M4 19.5A2.5 2.5 0 0 1 6.5 17H20'),
|
|
23
|
+
path('M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z'),
|
|
24
|
+
] },
|
|
25
|
+
cpu: { viewBox: '0 0 24 24', shapes: [
|
|
26
|
+
box(4, 4, 16, 16, 2), box(9, 9, 6, 6),
|
|
27
|
+
...['M15 2v2', 'M15 20v2', 'M2 15h2', 'M2 9h2', 'M20 15h2', 'M20 9h2', 'M9 2v2', 'M9 20v2'].map(path),
|
|
28
|
+
] },
|
|
29
|
+
memory: { viewBox: '0 0 24 24', shapes: [
|
|
30
|
+
path('M22 12s-3 1-5 1-3-1-5-1-3 1-5 1-5-1-5-1'), path('M7 21h10'), path('M9 21V10'), path('M15 21V10'),
|
|
31
|
+
path('M8 3h8a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z'),
|
|
32
|
+
] },
|
|
33
|
+
activity: { viewBox: '0 0 24 24', shapes: [['polyline', { points: '22 12 18 12 15 21 9 3 6 12 2 12' }]] },
|
|
34
|
+
database: { viewBox: '0 0 24 24', shapes: [
|
|
35
|
+
['ellipse', { cx: 12, cy: 5, rx: 9, ry: 3 }],
|
|
36
|
+
path('M3 5v14c0 1.66 4.03 3 9 3s9-1.34 9-3V5'),
|
|
37
|
+
path('M3 12c0 1.66 4.03 3 9 3s9-1.34 9-3'),
|
|
38
|
+
] },
|
|
39
|
+
google: { viewBox: '0 0 24 24', paint: 'none', shapes: [
|
|
40
|
+
['path', { fill: '#4285F4', d: 'M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z' }],
|
|
41
|
+
['path', { fill: '#34A853', d: 'M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z' }],
|
|
42
|
+
['path', { fill: '#FBBC05', d: 'M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z' }],
|
|
43
|
+
['path', { fill: '#EA4335', d: 'M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z' }],
|
|
44
|
+
] },
|
|
45
|
+
microsoft: { viewBox: '0 0 21 21', paint: 'none', shapes: [
|
|
46
|
+
['rect', { x: 1, y: 1, width: 9, height: 9, fill: '#F25022' }],
|
|
47
|
+
['rect', { x: 11, y: 1, width: 9, height: 9, fill: '#7FBA00' }],
|
|
48
|
+
['rect', { x: 1, y: 11, width: 9, height: 9, fill: '#00A4EF' }],
|
|
49
|
+
['rect', { x: 11, y: 11, width: 9, height: 9, fill: '#FFB900' }],
|
|
50
|
+
] },
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** The built-in set. */
|
|
54
|
+
export const ICONS = new IconSet(ICON_DEFS);
|
|
55
|
+
|
|
56
|
+
/** A built-in icon as an el() spec — `ICONS.spec(name, options)`. */
|
|
57
|
+
export const icon = (name, options) => ICONS.spec(name, options);
|
|
58
|
+
|
|
59
|
+
export { IconSet };
|
package/src/index.js
CHANGED
|
@@ -96,7 +96,7 @@ import { ArchimateSpecializationRegistry } from './archimateSpecializations.js';
|
|
|
96
96
|
import { ArchimateSpecializationSelector } from './charting/ArchimateSpecializationSelector.js';
|
|
97
97
|
import { ImageSelector } from './charting/ImageSelector.js';
|
|
98
98
|
import { CheckboxTreeSelector } from './charting/CheckboxTreeSelector.js';
|
|
99
|
-
import { SettingsManager, settingsManager } from './charting/SettingsManager.js';
|
|
99
|
+
import { SettingsManager, settingsManager, TREE_SETTINGS_PROPERTIES } from './charting/SettingsManager.js';
|
|
100
100
|
import { showGlobalSettingsDialog } from './charting/SettingsDialog.js';
|
|
101
101
|
import { ArchimateModel, ArchimateView } from './charting/diagrams/ArchimateModel.js';
|
|
102
102
|
import { CanvasView } from './charting/diagrams/CanvasView.js';
|
|
@@ -144,6 +144,7 @@ import { PopupMenu } from './charting/PopupMenu.js';
|
|
|
144
144
|
import { ViewTabStrip } from './charting/ViewTabStrip.js';
|
|
145
145
|
import { EditorShell } from './workspace/EditorShell.js';
|
|
146
146
|
import { FormDialog } from './charting/FormDialog.js';
|
|
147
|
+
import { Dialog } from './charting/Dialog.js';
|
|
147
148
|
import { ColorField } from './charting/ColorField.js';
|
|
148
149
|
import { TextStyle } from './charting/TextStyle.js';
|
|
149
150
|
import { ArchimateModelChecker } from './charting/diagrams/ArchimateModelChecker.js';
|
|
@@ -158,6 +159,10 @@ import { PropertiesPanel } from './charting/PropertiesPanel.js';
|
|
|
158
159
|
import { MenuBar } from './charting/MenuBar.js';
|
|
159
160
|
import { ArchimateDiagramEditor } from './charting/ArchimateDiagramEditor.js';
|
|
160
161
|
import { ModelTree } from './charting/modelTree/ModelTree.js';
|
|
162
|
+
import { TreeBuilder } from './charting/modelTree/TreeBuilder.js';
|
|
163
|
+
import { ContentSource } from './charting/content/ContentSource.js';
|
|
164
|
+
import { ContentSources } from './charting/content/ContentSources.js';
|
|
165
|
+
import { DUPLICATE_OPTIONS, NOISE_WORDS, FILLER_WORDS, duplicateGroups, duplicateKey, wordKey, nameKey } from './charting/diagrams/duplicates.js';
|
|
161
166
|
import { TreeModeRegistry } from './charting/modelTree/TreeModeRegistry.js';
|
|
162
167
|
import { TREE_MODE_CATALOG, treeModeRegistry, getTreeModeCatalogEntry } from './treeModeCatalog.js';
|
|
163
168
|
import { buildModelTreePanel } from './charting/modelTree/ModelTreePanel.js';
|
|
@@ -166,7 +171,7 @@ import { buildTypeCategoryTree, buildPropertyTree } from './charting/modelTree/b
|
|
|
166
171
|
import { DiagramTabs } from './charting/modelTree/DiagramTabs.js';
|
|
167
172
|
import { DataModelNotation, DataModelLevel, AttributedLevel, ConceptualLevel, LogicalLevel, PhysicalLevel, NotationStyle, RelationshipKind, AssociationKind, GeneralizationKind, EntityRole, FactRole, DimensionRole } from './datamodel.js';
|
|
168
173
|
import { DataModel, DataModelPerspective, DataModelContext, LevelSchema, LevelMapping, ServiceDomain, DataModelFileFormat, ServiceLandscape, BianLandscapeReader, BianSemanticApiReader, DataType, EnumerationType, StructuredType, NamingStandard, NameStyle, ClassificationScheme, ClassificationLens, SqlDdl, SqlDdlFileFormat, ModelScorecard, ScorecardCategory, MeasuredCategory } from './charting/datamodel/index.js';
|
|
169
|
-
import { RequirementsModel, RequirementsChecker, RequirementsFileFormat, TrackerExportFileFormat, StoryboardFileFormat, TrackerExport, TRACKERS, Storyboard } from './charting/requirements/index.js';
|
|
174
|
+
import { RequirementsModel, RequirementsChecker, RequirementsFileFormat, TrackerExportFileFormat, StoryboardFileFormat, TrackerExport, TRACKERS, Storyboard, TrackerApi, JiraApi, AzureDevOpsApi, GitLabApi, TRACKER_APIS, trackerApi, trackerConnectionFields } from './charting/requirements/index.js';
|
|
170
175
|
import { EditableModel } from './charting/model/EditableModel.js';
|
|
171
176
|
import { ModelEditCommand } from './charting/model/ModelEditCommand.js';
|
|
172
177
|
import { Benchmark } from './charting/model/Benchmark.js';
|
|
@@ -215,20 +220,22 @@ export {
|
|
|
215
220
|
TREE_MODE_CATALOG, treeModeRegistry, getTreeModeCatalogEntry,
|
|
216
221
|
AXIS_KIND, ENCODING_CHANNEL, COORD_SYSTEM, COLOR_MODE, CAMERA_MODE,
|
|
217
222
|
Command, CompositeCommand, CommandHistory,
|
|
218
|
-
DiagramEditor, DiagramEditorState, Template, PolygonTemplate, RegularPolygonTemplate, RectangleTemplate, RoundedRectangleTemplate, StadiumTemplate, PredefinedProcessTemplate, ClassTemplate, ComponentTemplate, SwimlaneTemplate, CircleTemplate, FinalStateTemplate, EllipseTemplate, DiamondTemplate, TriangleTemplate, PentagonTemplate, HexagonTemplate, OctagonTemplate, StarTemplate, ParallelogramTemplate, TrapezoidTemplate, ManualInputTemplate, CylinderTemplate, DocumentTemplate, NoteTemplate, CloudTemplate, ActorTemplate, PackageTemplate, BoxTemplate, DataStoreTemplate, LifelineTemplate, TemplateCatalog, Palette, PropertiesPanel, MenuBar, MoveElementCommand, BringToFrontCommand, SendToBackCommand, RaiseCommand, LowerCommand, CreateTemplateCommand, EditPropertyCommand, ResizeCommand, RotateCommand, SetPropertiesCommand, ReorderCommand, DIAGRAM_TYPES, getTemplatesForDiagramType, getAllDiagramTypes, getDiagramTypesByGroup, EdgeMarker, ArrowMarker, DiamondMarker, CircleMarker, CrowsFootMarker, HollowDiamondMarker, HollowTriangleMarker, OpenArrowMarker, HalfArrowMarker, EDGE_MARKERS, getEdgeMarker, Edge, AddEdgeCommand, DeleteEdgeCommand, ReconnectEdgeCommand, EDGE_STYLE_PROPERTIES, LINE_ONLY_PROPERTIES, LINE_STYLE_DASH, EdgeStyleCascade, SetStyleCommand, EdgeStyleDialog, DiagramModel, DiagramView, ViewSetModel, ViewMenu, ViewPropertiesDialog, SettingsViewPropertiesDialog, DiagramViewPropertiesDialog, inheritableFields, readInheritableFields, LocalJsonStore, PopupMenu, ViewTabStrip, EditorShell, FormDialog, ToolbarItem, ToolbarButton, ToolbarMenu, ToolbarSelect, ToolbarCheckbox, ToolbarText, ToolbarSeparator, pickFile, reorderedZ, stackingOrder, MoveWaypointCommand, AddWaypointCommand, DeleteWaypointCommand,
|
|
223
|
+
DiagramEditor, DiagramEditorState, Template, PolygonTemplate, RegularPolygonTemplate, RectangleTemplate, RoundedRectangleTemplate, StadiumTemplate, PredefinedProcessTemplate, ClassTemplate, ComponentTemplate, SwimlaneTemplate, CircleTemplate, FinalStateTemplate, EllipseTemplate, DiamondTemplate, TriangleTemplate, PentagonTemplate, HexagonTemplate, OctagonTemplate, StarTemplate, ParallelogramTemplate, TrapezoidTemplate, ManualInputTemplate, CylinderTemplate, DocumentTemplate, NoteTemplate, CloudTemplate, ActorTemplate, PackageTemplate, BoxTemplate, DataStoreTemplate, LifelineTemplate, TemplateCatalog, Palette, PropertiesPanel, MenuBar, MoveElementCommand, BringToFrontCommand, SendToBackCommand, RaiseCommand, LowerCommand, CreateTemplateCommand, EditPropertyCommand, ResizeCommand, RotateCommand, SetPropertiesCommand, ReorderCommand, DIAGRAM_TYPES, getTemplatesForDiagramType, getAllDiagramTypes, getDiagramTypesByGroup, EdgeMarker, ArrowMarker, DiamondMarker, CircleMarker, CrowsFootMarker, HollowDiamondMarker, HollowTriangleMarker, OpenArrowMarker, HalfArrowMarker, EDGE_MARKERS, getEdgeMarker, Edge, AddEdgeCommand, DeleteEdgeCommand, ReconnectEdgeCommand, EDGE_STYLE_PROPERTIES, LINE_ONLY_PROPERTIES, LINE_STYLE_DASH, EdgeStyleCascade, SetStyleCommand, EdgeStyleDialog, DiagramModel, DiagramView, ViewSetModel, ViewMenu, ViewPropertiesDialog, SettingsViewPropertiesDialog, DiagramViewPropertiesDialog, inheritableFields, readInheritableFields, LocalJsonStore, PopupMenu, ViewTabStrip, EditorShell, Dialog, FormDialog, ToolbarItem, ToolbarButton, ToolbarMenu, ToolbarSelect, ToolbarCheckbox, ToolbarText, ToolbarSeparator, pickFile, reorderedZ, stackingOrder, MoveWaypointCommand, AddWaypointCommand, DeleteWaypointCommand,
|
|
219
224
|
ebnfAstToDiagram, parseEbnf,
|
|
220
225
|
createDataWindow, GraphDataWindow,
|
|
221
226
|
ArchimateNotation, ArchimateLayer, ArchimateElementType, ArchimateRelationshipType,
|
|
222
227
|
Glyph, GLYPHS, glyphFor, drawGlyph, glyphElement, glyphToSvg, GlyphTemplate, ArchimateElementTemplate, ArchimateEdge, ArchimateCanvasEditor, ArchimatePalette, KIND_PREFERENCE, ARCHIMATE_TYPE_MIME,
|
|
223
228
|
ArchimateModel, ArchimateView, CanvasView, PlaceItemsCommand, RemoveLineCommand, CanvasBoxKind, CANVAS_BOX_KINDS, canvasBoxKind, canvasBoxMenuItems, ArchiHint, HintPanel, EditHintCommand, hintMenuItems, CanvasPalette, CANVAS_BOX_MIME, ViewLineEdge, Arrangement, ItemGroup, BackgroundGroup, GroupCommand, UngroupCommand, LockCommand, BackgroundCommand, arrangeMenuItems, TemplateLibrary, CanvasTemplateLibrary, CanvasTemplateMenu, DEFAULT_CANVAS_TEMPLATES, canvasTemplate, EditViewItemCommand, viewItemChanges, ColorField, TextStyle, ArchimateModelChecker, ViewSetChecker, ViewFit, archiElementType, NEAREST_ARCHI_TYPE, ElementInstance, ViewObjectInstance, RelationshipInstance, ArchimateDiagramEditor,
|
|
224
229
|
ITIconNotation, ITElementType,
|
|
225
|
-
ArchimateValidator, ArchimateSpecializationSelector, ImageSelector, CheckboxTreeSelector, SettingsManager, settingsManager,
|
|
226
|
-
ModelTree, TreeModeRegistry,
|
|
230
|
+
ArchimateValidator, ArchimateSpecializationSelector, ImageSelector, CheckboxTreeSelector, SettingsManager, settingsManager, TREE_SETTINGS_PROPERTIES,
|
|
231
|
+
ModelTree, TreeBuilder, TreeModeRegistry, ContentSource, ContentSources,
|
|
232
|
+
DUPLICATE_OPTIONS, NOISE_WORDS, FILLER_WORDS, duplicateGroups, duplicateKey, wordKey, nameKey,
|
|
227
233
|
RadialLayout, DerivedRelationships, EdgeGeometry,
|
|
228
234
|
buildModelTreePanel, buildAnalysisPanel, buildAnalysisData, buildTypeCategoryTree, buildPropertyTree, DiagramTabs,
|
|
229
235
|
DataModelNotation, DataModelLevel, AttributedLevel, ConceptualLevel, LogicalLevel, PhysicalLevel, NotationStyle, RelationshipKind, AssociationKind, GeneralizationKind, EntityRole, FactRole, DimensionRole,
|
|
230
236
|
DataModel, DataModelPerspective, DataModelContext, LevelSchema, LevelMapping, ServiceDomain, DataModelFileFormat, ServiceLandscape, BianLandscapeReader, BianSemanticApiReader, DataType, EnumerationType, StructuredType, NamingStandard, NameStyle, ClassificationScheme, ClassificationLens, SqlDdl, SqlDdlFileFormat, ModelScorecard, ScorecardCategory, MeasuredCategory,
|
|
231
237
|
RequirementsModel, RequirementsChecker, RequirementsFileFormat, TrackerExportFileFormat, StoryboardFileFormat, TrackerExport, TRACKERS, Storyboard,
|
|
238
|
+
TrackerApi, JiraApi, AzureDevOpsApi, GitLabApi, TRACKER_APIS, trackerApi, trackerConnectionFields,
|
|
232
239
|
EditableModel, ModelEditCommand, Benchmark,
|
|
233
240
|
BianNotation, BianFunctionalPattern, BianActionTerm,
|
|
234
241
|
exportElementsCSV, exportRelationshipsCSV, exportModelCSV,
|
|
@@ -250,6 +257,11 @@ export {
|
|
|
250
257
|
* @class RenderEngine
|
|
251
258
|
* This class decides which renderer to use and provides a unified drawing API.
|
|
252
259
|
*/
|
|
260
|
+
// Navigation menus (a sidebar's): DOM built from JSON, safe to import under node.
|
|
261
|
+
export { NavEntry, NavItem, NavGroup, NavSection, NavMenu } from './workspace/nav/index.js';
|
|
262
|
+
// Named icons as data (an <svg> spec each); the built-in pool and its class.
|
|
263
|
+
export { IconSet, ICONS, ICON_DEFS, icon } from './icons/index.js';
|
|
264
|
+
|
|
253
265
|
export class RenderEngine {
|
|
254
266
|
/** Layout constants for graphDiagram's `shape: 'table'` node type. */
|
|
255
267
|
static TABLE_NODE = NodeGeometry.TABLE_NODE;
|
|
@@ -438,8 +450,12 @@ export class RenderEngine {
|
|
|
438
450
|
* @param {HTMLElement} container The container element for the chart.
|
|
439
451
|
* @param {object} [options]
|
|
440
452
|
* @param {'canvas' | 'svg'} [options.rendererType='canvas']
|
|
453
|
+
* @param {false|{wheelModifier?: 'ctrl'|'alt'|'shift'}} [options.zoom] - SVG wheel zoom: off, or only
|
|
454
|
+
* with a modifier held (so a plain wheel scrolls the page); on, with a plain wheel, by default
|
|
441
455
|
*/
|
|
442
456
|
constructor(container, options = {}) {
|
|
457
|
+
// Kept: the engines refreshChart() makes for other containers are made with the same options.
|
|
458
|
+
this.options = options;
|
|
443
459
|
if (container) {
|
|
444
460
|
const { rendererType = 'canvas' } = options;
|
|
445
461
|
const width = container.clientWidth;
|
|
@@ -447,11 +463,9 @@ export class RenderEngine {
|
|
|
447
463
|
|
|
448
464
|
this.container = container; // Store container for event listeners
|
|
449
465
|
this.renderer = RendererFactory.create(rendererType, container, width, height);
|
|
450
|
-
} else {
|
|
451
|
-
// Allow initialization without a container for testing
|
|
452
|
-
this.options = options;
|
|
453
|
-
console.log('RenderEngine initialized in test mode.');
|
|
454
466
|
}
|
|
467
|
+
// No container: a test, or an engine keeping a page of registered
|
|
468
|
+
// charts, each drawn in its own element (refreshChart).
|
|
455
469
|
|
|
456
470
|
if (this.container) {
|
|
457
471
|
this._interactions = new InteractionManager(this);
|
|
@@ -2587,7 +2601,10 @@ export class RenderEngine {
|
|
|
2587
2601
|
|
|
2588
2602
|
/**
|
|
2589
2603
|
* Refreshes a registered chart, re-resolving its data and re-invoking
|
|
2590
|
-
* its render method
|
|
2604
|
+
* its render method, in the element with that id: this engine's own
|
|
2605
|
+
* container, or — so one engine can keep a page of charts — an engine of
|
|
2606
|
+
* its own for that element, made with this engine's options and kept
|
|
2607
|
+
* (so a zoom survives the next refresh) while the element is the same.
|
|
2591
2608
|
* @param {string} id Chart container ID
|
|
2592
2609
|
*/
|
|
2593
2610
|
refreshChart(id) {
|
|
@@ -2595,7 +2612,46 @@ export class RenderEngine {
|
|
|
2595
2612
|
const config = this.registry[id];
|
|
2596
2613
|
const container = document.getElementById(id);
|
|
2597
2614
|
if (!container) return;
|
|
2615
|
+
const target = this._engineFor(id, container);
|
|
2616
|
+
if (target !== this) target.fitToContainer();
|
|
2617
|
+
target._drawRegistered(config);
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
/** Every registered chart, refreshed. */
|
|
2621
|
+
refreshAll() {
|
|
2622
|
+
for (const id of Object.keys(this.registry ?? {})) this.refreshChart(id);
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
/**
|
|
2626
|
+
* Set the data registered charts read by `dataPath`, and refresh them all —
|
|
2627
|
+
* a live page's poll: `engine.setData(await fetchMetrics())`.
|
|
2628
|
+
* @param {object} data
|
|
2629
|
+
*/
|
|
2630
|
+
setData(data) {
|
|
2631
|
+
this.setGlobalData(data);
|
|
2632
|
+
this.refreshAll();
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
/** The engine that draws into `container` for registered chart `id`. @private */
|
|
2636
|
+
_engineFor(id, container) {
|
|
2637
|
+
if (container === this.container) return this;
|
|
2638
|
+
this._childEngines ??= new Map();
|
|
2639
|
+
let engine = this._childEngines.get(id);
|
|
2640
|
+
if (engine?.container !== container) {
|
|
2641
|
+
engine = this._newEngine(container);
|
|
2642
|
+
this._childEngines.set(id, engine);
|
|
2643
|
+
}
|
|
2644
|
+
engine.globalData = this.globalData;
|
|
2645
|
+
return engine;
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
/** An engine for another container, with this one's options. @private */
|
|
2649
|
+
_newEngine(container) {
|
|
2650
|
+
return new RenderEngine(container, this.options);
|
|
2651
|
+
}
|
|
2598
2652
|
|
|
2653
|
+
/** Draw a registration (`{type, options}`) with this engine. @private */
|
|
2654
|
+
_drawRegistered(config) {
|
|
2599
2655
|
// Catalog keys ('bar', 'heatmap', 'graph', 'archimate', ...) resolve
|
|
2600
2656
|
// through the registries to their real method name; railroadDiagram
|
|
2601
2657
|
// isn't a registry entry, so `type` already IS the method name there.
|
|
@@ -2612,6 +2668,7 @@ export class RenderEngine {
|
|
|
2612
2668
|
this[methodName](config.options.nodes, config.options.edges || [], config.options);
|
|
2613
2669
|
} else {
|
|
2614
2670
|
const data = config.options.data || this.resolvePath(config.options.dataPath, config.options.dataMapper);
|
|
2671
|
+
if (data === undefined) return; // registered before its data has arrived
|
|
2615
2672
|
this[methodName](data, config.options);
|
|
2616
2673
|
}
|
|
2617
2674
|
}
|
package/src/workspace/dom.js
CHANGED
|
@@ -20,11 +20,20 @@
|
|
|
20
20
|
* on: { click: fn }, // addEventListener
|
|
21
21
|
* children: [spec | Node | string], // strings become text nodes
|
|
22
22
|
* ref: (element) => {}, // capture the node without querySelector
|
|
23
|
+
* ns: SVG_NS, // the element's namespace (see below)
|
|
23
24
|
* }
|
|
24
25
|
*
|
|
25
26
|
* A bare string spec produces a text node, so `children: ['plain text']` works.
|
|
27
|
+
*
|
|
28
|
+
* An `svg` spec, and everything inside it, is built in the SVG namespace
|
|
29
|
+
* (`createElementNS`) — so a drawing or an icon is a spec too, never parsed
|
|
30
|
+
* markup — until a `foreignObject`, whose children are HTML again. `ns` sets
|
|
31
|
+
* a namespace explicitly (a `path` spec built on its own, to append to an
|
|
32
|
+
* existing <svg>).
|
|
26
33
|
*/
|
|
27
|
-
export
|
|
34
|
+
export const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
35
|
+
|
|
36
|
+
export function el(spec, doc = document, ns = null) {
|
|
28
37
|
if (spec == null) return doc.createTextNode('');
|
|
29
38
|
if (typeof spec === 'string' || typeof spec === 'number') return doc.createTextNode(String(spec));
|
|
30
39
|
// Duck-typed rather than `instanceof Node`: `Node` is a browser global, so
|
|
@@ -32,11 +41,15 @@ export function el(spec, doc = document) {
|
|
|
32
41
|
// substituted document. A spec object never carries either of these.
|
|
33
42
|
if (typeof spec.nodeType === 'number' || typeof spec.appendChild === 'function') return spec;
|
|
34
43
|
|
|
35
|
-
const
|
|
44
|
+
const tag = spec.tag ?? 'div';
|
|
45
|
+
const space = spec.ns ?? (tag === 'svg' ? SVG_NS : ns);
|
|
46
|
+
const node = space ? doc.createElementNS(space, tag) : doc.createElement(tag);
|
|
36
47
|
|
|
37
48
|
if (spec.cls) {
|
|
38
|
-
const classes = Array.isArray(spec.cls) ? spec.cls : [spec.cls];
|
|
39
|
-
|
|
49
|
+
const classes = (Array.isArray(spec.cls) ? spec.cls : [spec.cls]).filter(Boolean).join(' ');
|
|
50
|
+
// An SVG element's className is an SVGAnimatedString, not a string.
|
|
51
|
+
if (space) node.setAttribute('class', classes);
|
|
52
|
+
else node.className = classes;
|
|
40
53
|
}
|
|
41
54
|
if (spec.text != null) node.textContent = String(spec.text);
|
|
42
55
|
|
|
@@ -55,8 +68,9 @@ export function el(spec, doc = document) {
|
|
|
55
68
|
for (const [event, handler] of Object.entries(spec.on ?? {})) {
|
|
56
69
|
if (typeof handler === 'function') node.addEventListener(event, handler);
|
|
57
70
|
}
|
|
71
|
+
const childSpace = space && tag !== 'foreignObject' ? space : null;
|
|
58
72
|
for (const child of spec.children ?? []) {
|
|
59
|
-
node.appendChild(el(child, doc));
|
|
73
|
+
node.appendChild(el(child, doc, childSpace));
|
|
60
74
|
}
|
|
61
75
|
|
|
62
76
|
spec.ref?.(node);
|
|
@@ -81,40 +95,40 @@ export function render(host, specs, doc = document) {
|
|
|
81
95
|
function cssProp(key) {
|
|
82
96
|
return key.startsWith('--') ? key : key.replace(/[A-Z]/g, c => `-${c.toLowerCase()}`);
|
|
83
97
|
}
|
|
84
|
-
|
|
85
|
-
const VOID_TAGS = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr']);
|
|
86
|
-
const escapeHtml = (s) => String(s).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]);
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* The same spec as el(), written out as HTML text: for a page that is saved
|
|
90
|
-
* rather than shown (a report). Text and attribute values are escaped; `on`
|
|
91
|
-
* and `ref` have no meaning in text and are left out. `script` is the one
|
|
92
|
-
* extra field: the trusted contents of a <script> or <style> (not escaped,
|
|
93
|
-
* but `</` is, so it cannot close its element early).
|
|
94
|
-
*/
|
|
95
|
-
export function toHtml(spec) {
|
|
96
|
-
if (spec == null) return '';
|
|
97
|
-
if (typeof spec === 'string' || typeof spec === 'number') return escapeHtml(spec);
|
|
98
|
-
if (Array.isArray(spec)) return spec.map(toHtml).join('');
|
|
99
|
-
const tag = spec.tag ?? 'div';
|
|
100
|
-
const attrs = [];
|
|
101
|
-
if (spec.cls) {
|
|
102
|
-
const classes = (Array.isArray(spec.cls) ? spec.cls : [spec.cls]).filter(Boolean);
|
|
103
|
-
if (classes.length) attrs.push(['class', classes.join(' ')]);
|
|
104
|
-
}
|
|
105
|
-
for (const [k, v] of Object.entries(spec.attrs ?? {})) {
|
|
106
|
-
if (v == null || v === false) continue;
|
|
107
|
-
attrs.push([k, v === true ? '' : String(v)]);
|
|
108
|
-
}
|
|
109
|
-
for (const [k, v] of Object.entries(spec.dataset ?? {})) {
|
|
110
|
-
if (v != null) attrs.push([`data-${cssProp(k)}`, String(v)]);
|
|
111
|
-
}
|
|
112
|
-
const style = Object.entries(spec.style ?? {}).filter(([, v]) => v != null).map(([k, v]) => `${cssProp(k)}: ${v}`).join('; ');
|
|
113
|
-
if (style) attrs.push(['style', style]);
|
|
114
|
-
const open = `<${tag}${attrs.map(([k, v]) => ` ${k}="${escapeHtml(v)}"`).join('')}>`;
|
|
115
|
-
if (VOID_TAGS.has(tag)) return open;
|
|
116
|
-
const inner = spec.script != null
|
|
117
|
-
? String(spec.script).replace(/<\//g, '<\\/')
|
|
118
|
-
: (spec.text != null ? escapeHtml(spec.text) : '') + (spec.children ?? []).map(toHtml).join('');
|
|
119
|
-
return `${open}${inner}</${tag}>`;
|
|
120
|
-
}
|
|
98
|
+
|
|
99
|
+
const VOID_TAGS = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr']);
|
|
100
|
+
const escapeHtml = (s) => String(s).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]);
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The same spec as el(), written out as HTML text: for a page that is saved
|
|
104
|
+
* rather than shown (a report). Text and attribute values are escaped; `on`
|
|
105
|
+
* and `ref` have no meaning in text and are left out. `script` is the one
|
|
106
|
+
* extra field: the trusted contents of a <script> or <style> (not escaped,
|
|
107
|
+
* but `</` is, so it cannot close its element early).
|
|
108
|
+
*/
|
|
109
|
+
export function toHtml(spec) {
|
|
110
|
+
if (spec == null) return '';
|
|
111
|
+
if (typeof spec === 'string' || typeof spec === 'number') return escapeHtml(spec);
|
|
112
|
+
if (Array.isArray(spec)) return spec.map(toHtml).join('');
|
|
113
|
+
const tag = spec.tag ?? 'div';
|
|
114
|
+
const attrs = [];
|
|
115
|
+
if (spec.cls) {
|
|
116
|
+
const classes = (Array.isArray(spec.cls) ? spec.cls : [spec.cls]).filter(Boolean);
|
|
117
|
+
if (classes.length) attrs.push(['class', classes.join(' ')]);
|
|
118
|
+
}
|
|
119
|
+
for (const [k, v] of Object.entries(spec.attrs ?? {})) {
|
|
120
|
+
if (v == null || v === false) continue;
|
|
121
|
+
attrs.push([k, v === true ? '' : String(v)]);
|
|
122
|
+
}
|
|
123
|
+
for (const [k, v] of Object.entries(spec.dataset ?? {})) {
|
|
124
|
+
if (v != null) attrs.push([`data-${cssProp(k)}`, String(v)]);
|
|
125
|
+
}
|
|
126
|
+
const style = Object.entries(spec.style ?? {}).filter(([, v]) => v != null).map(([k, v]) => `${cssProp(k)}: ${v}`).join('; ');
|
|
127
|
+
if (style) attrs.push(['style', style]);
|
|
128
|
+
const open = `<${tag}${attrs.map(([k, v]) => ` ${k}="${escapeHtml(v)}"`).join('')}>`;
|
|
129
|
+
if (VOID_TAGS.has(tag)) return open;
|
|
130
|
+
const inner = spec.script != null
|
|
131
|
+
? String(spec.script).replace(/<\//g, '<\\/')
|
|
132
|
+
: (spec.text != null ? escapeHtml(spec.text) : '') + (spec.children ?? []).map(toHtml).join('');
|
|
133
|
+
return `${open}${inner}</${tag}>`;
|
|
134
|
+
}
|
package/src/workspace/index.js
CHANGED
|
@@ -42,7 +42,7 @@ export {
|
|
|
42
42
|
ContextBus, Part, PartRegistry, PartFactory,
|
|
43
43
|
ContentPart, HtmlPart, ListPart, DetailPart, AdapterPart, ModelsPart,
|
|
44
44
|
};
|
|
45
|
-
export { el, frag, render } from './dom.js';
|
|
45
|
+
export { el, frag, render, SVG_NS } from './dom.js';
|
|
46
46
|
|
|
47
47
|
// The engine's whole public API, re-exported so the workspace UMD bundle is a
|
|
48
48
|
// superset of the main one: a page that loads renderengine-workspace.umd.js
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NavEntry — one entry in a NavMenu: an item, a group of items, or a section
|
|
3
|
+
* heading. Subclasses return an el() spec; the menu builds every entry from
|
|
4
|
+
* JSON (NavMenu.entry), so a host writes its navigation as data, not markup.
|
|
5
|
+
*
|
|
6
|
+
* `visible` is a boolean or a function of the menu's context (`{ role }`, …),
|
|
7
|
+
* so one definition serves every user and the menu re-renders when the
|
|
8
|
+
* context changes (NavMenu#setContext).
|
|
9
|
+
*/
|
|
10
|
+
export class NavEntry {
|
|
11
|
+
/**
|
|
12
|
+
* @param {Object} [options]
|
|
13
|
+
* @param {string} [options.id]
|
|
14
|
+
* @param {string|string[]} [options.cls] - Extra class names
|
|
15
|
+
* @param {string} [options.title] - Tooltip
|
|
16
|
+
* @param {boolean|((context: Object) => boolean)} [options.visible]
|
|
17
|
+
*/
|
|
18
|
+
constructor({ id = null, cls = null, title = null, visible = true } = {}) {
|
|
19
|
+
this.id = id;
|
|
20
|
+
this.cls = cls;
|
|
21
|
+
this.title = title;
|
|
22
|
+
this.visible = visible;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Whether the entry shows, given the menu's context. */
|
|
26
|
+
isVisible(menu) {
|
|
27
|
+
return typeof this.visible === 'function' ? !!this.visible(menu.context) : this.visible !== false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** @returns {Object} el() spec */
|
|
31
|
+
spec(menu) {
|
|
32
|
+
throw new Error(`${this.constructor.name} must implement spec()`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The entries this one holds (a group's items); none by default. */
|
|
36
|
+
get children() { return []; }
|
|
37
|
+
|
|
38
|
+
/** Extra class names as a list, for el()'s `cls`. */
|
|
39
|
+
extraClasses() {
|
|
40
|
+
return Array.isArray(this.cls) ? this.cls : this.cls ? [this.cls] : [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { NavEntry } from './NavEntry.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* NavGroup — entries under a heading that opens and closes (a `<details>`).
|
|
5
|
+
* Whether it is open is state the menu keeps (NavMenu#serialize) and reports
|
|
6
|
+
* (`onToggle`), so a host can remember it. Hidden when none of its entries
|
|
7
|
+
* shows.
|
|
8
|
+
*/
|
|
9
|
+
export class NavGroup extends NavEntry {
|
|
10
|
+
/**
|
|
11
|
+
* @param {Object} options
|
|
12
|
+
* @param {string} options.group - The heading's text
|
|
13
|
+
* @param {Array<Object|NavEntry>} options.items - Entries, as JSON or built
|
|
14
|
+
* @param {boolean} [options.open]
|
|
15
|
+
* @param {(json: Object) => NavEntry} entry - Builds an entry from JSON (NavMenu.entry)
|
|
16
|
+
*/
|
|
17
|
+
constructor({ group, items = [], open = false, ...rest }, entry) {
|
|
18
|
+
super(rest);
|
|
19
|
+
this.label = group;
|
|
20
|
+
this.open = !!open;
|
|
21
|
+
this.items = items.map(entry);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get children() { return this.items; }
|
|
25
|
+
|
|
26
|
+
isVisible(menu) {
|
|
27
|
+
return super.isVisible(menu) && this.items.some(e => e.isVisible(menu));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
spec(menu) {
|
|
31
|
+
return {
|
|
32
|
+
tag: 'details',
|
|
33
|
+
cls: ['ws-nav-group', ...this.extraClasses()],
|
|
34
|
+
attrs: { open: this.open },
|
|
35
|
+
dataset: { navId: this.id },
|
|
36
|
+
children: [
|
|
37
|
+
{ tag: 'summary', cls: 'ws-nav-group-summary', text: this.label, attrs: { title: this.title } },
|
|
38
|
+
...menu.specsOf(this.items),
|
|
39
|
+
],
|
|
40
|
+
on: { toggle: (event) => menu.toggled(this, !!event.target.open) },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { NavEntry } from './NavEntry.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* NavItem — a place to go or a thing to do. With `href` it is a link (the
|
|
5
|
+
* browser navigates, and the address can be opened in a new tab); without, a
|
|
6
|
+
* button. `action(event, item, menu)` runs on a click either way, and may
|
|
7
|
+
* call `event.preventDefault()` to keep a link from navigating — e.g. a
|
|
8
|
+
* modifier-click that opens the target beside the current view instead.
|
|
9
|
+
*/
|
|
10
|
+
export class NavItem extends NavEntry {
|
|
11
|
+
/**
|
|
12
|
+
* @param {Object} options
|
|
13
|
+
* @param {string} options.label
|
|
14
|
+
* @param {string} [options.icon] - Shown before the label, hidden from screen readers: an icon's
|
|
15
|
+
* name in the menu's IconSet (`icons`), or text (an emoji)
|
|
16
|
+
* @param {string|((context: Object) => string|null)} [options.href] - Or a function of the menu's context (an address known later)
|
|
17
|
+
* @param {(event: Event, item: NavItem, menu: NavMenu) => void} [options.action]
|
|
18
|
+
* @param {boolean} [options.selectable] - Whether a click makes it the active item (default: true)
|
|
19
|
+
*/
|
|
20
|
+
constructor({ label, icon = null, href = null, action = null, selectable = true, ...rest }) {
|
|
21
|
+
super(rest);
|
|
22
|
+
this.label = label;
|
|
23
|
+
this.icon = icon;
|
|
24
|
+
this.href = href;
|
|
25
|
+
this.action = action;
|
|
26
|
+
this.selectable = selectable;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
spec(menu) {
|
|
30
|
+
const active = this.id != null && this.id === menu.activeId;
|
|
31
|
+
const href = typeof this.href === 'function' ? this.href(menu.context) : this.href;
|
|
32
|
+
return {
|
|
33
|
+
tag: href != null ? 'a' : 'button',
|
|
34
|
+
cls: ['ws-nav-item', active && 'ws-nav-item-active', ...this.extraClasses()],
|
|
35
|
+
attrs: {
|
|
36
|
+
href,
|
|
37
|
+
type: href != null ? null : 'button',
|
|
38
|
+
title: this.title,
|
|
39
|
+
'aria-current': active ? 'page' : null,
|
|
40
|
+
},
|
|
41
|
+
dataset: { navId: this.id },
|
|
42
|
+
children: [
|
|
43
|
+
this.icon != null && {
|
|
44
|
+
tag: 'span', cls: 'ws-nav-icon', attrs: { 'aria-hidden': 'true' },
|
|
45
|
+
...(menu.icons?.has(this.icon) ? { children: [menu.icons.spec(this.icon, { size: 16 })] } : { text: this.icon }),
|
|
46
|
+
},
|
|
47
|
+
{ tag: 'span', cls: 'ws-nav-label', text: this.label },
|
|
48
|
+
].filter(Boolean),
|
|
49
|
+
on: { click: (event) => menu.select(this, event) },
|
|
50
|
+
ref: (n) => menu.nodes.set(this, n),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|