@likec4/core 1.34.1 → 1.35.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.
Files changed (56) hide show
  1. package/dist/builder/index.d.mts +2 -2
  2. package/dist/builder/index.mjs +11 -10
  3. package/dist/compute-view/index.d.mts +2 -2
  4. package/dist/compute-view/index.mjs +12 -11
  5. package/dist/compute-view/relationships-view/index.d.mts +2 -2
  6. package/dist/compute-view/relationships-view/index.mjs +2 -2
  7. package/dist/index.d.mts +2 -2
  8. package/dist/index.mjs +9 -8
  9. package/dist/model/connection/deployment/index.d.mts +4 -4
  10. package/dist/model/connection/deployment/index.mjs +3 -3
  11. package/dist/model/connection/index.d.mts +5 -5
  12. package/dist/model/connection/index.mjs +4 -4
  13. package/dist/model/connection/model/index.d.mts +4 -4
  14. package/dist/model/connection/model/index.mjs +3 -3
  15. package/dist/model/index.d.mts +31 -7
  16. package/dist/model/index.mjs +9 -9
  17. package/dist/shared/{core.D3xEWj5B.d.mts → core.72IoI17N.d.mts} +1 -1
  18. package/dist/shared/{core.DXRAwwDB.mjs → core.B1ZN723l.mjs} +1 -1
  19. package/dist/shared/core.BpJu1RrF.mjs +5 -0
  20. package/dist/shared/{core.C80Z37Eu.mjs → core.C1GV0WWV.mjs} +1 -1
  21. package/dist/shared/{core.D5qVzkws.mjs → core.CQS11dNK.mjs} +2 -2
  22. package/dist/shared/{core.C_C-3c1S.mjs → core.DQj9iv_y.mjs} +3 -5
  23. package/dist/shared/{core.IWLie-Ma.d.mts → core.Ddua-wiI.d.mts} +3 -0
  24. package/dist/shared/{core.CJSFKrYf.d.mts → core.DhTO8rgm.d.mts} +2 -2
  25. package/dist/shared/{core.CtKXPqN_.mjs → core.HUONfQfd.mjs} +1 -1
  26. package/dist/shared/{core.Di7__sMg.d.mts → core.NBNeVyag.d.mts} +2 -2
  27. package/dist/shared/{core.PqNe5WxQ.mjs → core.RnUJywKk.mjs} +94 -92
  28. package/dist/shared/{core.DrS4Bf4y.d.mts → core.SciGxb--.d.mts} +183 -8
  29. package/dist/shared/{core.BTnf14gD.mjs → core.Y3j3s-oq.mjs} +2 -2
  30. package/dist/shared/{core.XptQjhMA.mjs → core.b2BzqGz0.mjs} +2 -2
  31. package/dist/shared/{core.4SpVCM9m.mjs → core.rpF8esVq.mjs} +354 -28
  32. package/dist/shared/{core.BMCb0wJI.mjs → core.yaRvRnMW.mjs} +1 -1
  33. package/dist/theme/index.mjs +33 -3
  34. package/dist/types/index.d.mts +2 -2
  35. package/dist/types/index.mjs +2 -2
  36. package/dist/utils/index.d.mts +6 -8
  37. package/dist/utils/index.mjs +5 -5
  38. package/dist/utils/iterable/index.d.mts +18 -1
  39. package/dist/utils/iterable/index.mjs +22 -2
  40. package/package.json +6 -5
  41. package/src/compute-view/element-view/predicates/relation-direct.ts +8 -2
  42. package/src/model/DeploymentModel.ts +1 -1
  43. package/src/model/LikeC4Model.ts +146 -7
  44. package/src/model/index.ts +8 -0
  45. package/src/model/types.ts +1 -8
  46. package/src/model/utils.ts +66 -0
  47. package/src/model/view/LikeC4ViewModel.ts +62 -7
  48. package/src/model/view/LikeC4ViewsFolder.ts +151 -0
  49. package/src/theme/__test__/theme-index.ts +29 -0
  50. package/src/theme/index.ts +49 -3
  51. package/src/types/model-spec.ts +4 -0
  52. package/src/utils/compare-natural.ts +14 -12
  53. package/src/utils/index.ts +1 -0
  54. package/src/utils/iterable/first.ts +48 -0
  55. package/src/utils/iterable/index.ts +1 -0
  56. package/dist/shared/{core.Br7Fab9l.mjs → core.BajmN8zR.mjs} +6 -6
@@ -1,24 +1,29 @@
1
1
  import { u as u$1 } from './core.DbRvwARP.mjs';
2
- import { p } from './core.BTnf14gD.mjs';
2
+ import { p } from './core.Y3j3s-oq.mjs';
3
3
  import { m as m$2 } from './core.ByrmCl9I.mjs';
4
- import { g as hierarchyLevel, n as nameFromFqn, i as isAncestor, d as commonAncestor, l as sortNaturalByFqn, m as sortParentsFirst, p as parentFqn, b as ancestorsFqn, C, c as compareNatural } from './core.C_C-3c1S.mjs';
4
+ import { i as i$2 } from './core.CpwrZi3D.mjs';
5
+ import { g as hierarchyLevel, n as nameFromFqn, i as isAncestor, d as commonAncestor, l as sortNaturalByFqn, m as sortParentsFirst, p as parentFqn, b as ancestorsFqn, C, a as compareNaturalHierarchically, c as compareNatural } from './core.DQj9iv_y.mjs';
5
6
  import { g as getOrCreate } from './core.DcS-0zys.mjs';
6
- import { i, n as n$2, e as DeploymentNodeModel, c as DeployedInstanceModel, N as NestedElementOfDeployedInstanceModel, g as DeploymentRelationModel } from './core.D5qVzkws.mjs';
7
- import { D as DefaultMap } from './core.BMCb0wJI.mjs';
8
- import { a as nonNullable, i as invariant } from './core.D4npX2q8.mjs';
9
- import { s as splitGlobalFqn, F as FqnRef, b as isDeploymentNode, r as extractStep, q as isStepEdgeId, m as isGroupElementKind, a as _type, _ as _stage, i as isOnStage, w as whereOperatorAsPredicate, o as isGlobalFqn, n as GlobalFqn } from './core.Br7Fab9l.mjs';
7
+ import { i as i$1, n as n$3, e as DeploymentNodeModel, c as DeployedInstanceModel, N as NestedElementOfDeployedInstanceModel, g as DeploymentRelationModel } from './core.CQS11dNK.mjs';
8
+ import { D as DefaultMap } from './core.yaRvRnMW.mjs';
9
+ import { i as invariant, a as nonNullable } from './core.D4npX2q8.mjs';
10
+ import { s as splitGlobalFqn, F as FqnRef, b as isDeploymentNode, r as extractStep, q as isStepEdgeId, m as isGroupElementKind, a as _type, _ as _stage, i as isOnStage, w as whereOperatorAsPredicate, o as isGlobalFqn, n as GlobalFqn } from './core.BajmN8zR.mjs';
10
11
  import { m as memoizeProp } from './core.B0oEIXqd.mjs';
11
12
  import { i as ifilter } from './core.BH9nN-_I.mjs';
12
- import { a as DefaultElementShape, D as DefaultThemeColor, R as RichText, b as DefaultShapeSize, n as n$1, g as DefaultRelationshipColor, e as DefaultLineStyle } from './core.D56nCp7e.mjs';
13
+ import { a as DefaultElementShape, D as DefaultThemeColor, R as RichText, b as DefaultShapeSize, n as n$2, g as DefaultRelationshipColor, e as DefaultLineStyle } from './core.D56nCp7e.mjs';
13
14
  import { a as ihead, i as ifind } from './core.DsMrjY2q.mjs';
14
15
 
15
16
  function r(...t){return u$1(Object.values,t)}
16
17
 
17
18
  var T={asc:(r,n)=>r>n,desc:(r,n)=>r<n};function s(r,n){let[e,...o]=n;if(!m$1(e)){let t=u(...o);return r(e,t)}let a=u(e,...o);return t=>r(t,a)}function u(r,n,...e){let o=typeof r=="function"?r:r[0],a=typeof r=="function"?"asc":r[1],{[a]:t}=T,i=n===void 0?void 0:u(n,...e);return (y,c)=>{let p=o(y),l=o(c);return t(p,l)?1:t(l,p)?-1:i?.(y,c)??0}}function m$1(r){if(d(r))return true;if(typeof r!="object"||!Array.isArray(r))return false;let[n,e,...o]=r;return d(n)&&typeof e=="string"&&e in T&&o.length===0}var d=r=>typeof r=="function"&&r.length===1;
18
19
 
20
+ function i(t,e,n){return typeof e=="number"||e===void 0?r=>r.split(t,e):t.split(e,n)}
21
+
19
22
  function m(...r){return u$1(o,r)}function o(r,t){let e=[...r];return e.sort(t),e}
20
23
 
21
- function a(...r){return s(n,r)}var n=(r,t)=>[...r].sort(t);
24
+ function a$1(...r){return s(n$1,r)}var n$1=(r,t)=>[...r].sort(t);
25
+
26
+ function a(...e){return u$1(n,e)}var n=e=>e.at(-1);
22
27
 
23
28
  function t(...r){return u$1(Object.entries,r)}
24
29
 
@@ -68,7 +73,7 @@ class ElementModel {
68
73
  */
69
74
  get tags() {
70
75
  return memoizeProp(this, Symbol.for("tags"), () => {
71
- return i([
76
+ return i$1([
72
77
  ...this.$element.tags ?? [],
73
78
  ...this.$model.specification.elements[this.$element.kind]?.tags ?? []
74
79
  ]);
@@ -226,13 +231,13 @@ class ElementModel {
226
231
  * @returns true if the element is deployed
227
232
  */
228
233
  isDeployed() {
229
- return n$1(ihead(this.deployments()));
234
+ return n$2(ihead(this.deployments()));
230
235
  }
231
236
  deployments() {
232
237
  return this.$model.deployment.instancesOf(this);
233
238
  }
234
239
  hasMetadata() {
235
- return !!this.$element.metadata && !n$2(this.$element.metadata);
240
+ return !!this.$element.metadata && !n$3(this.$element.metadata);
236
241
  }
237
242
  getMetadata(field) {
238
243
  if (field) {
@@ -248,9 +253,37 @@ class ElementModel {
248
253
  }
249
254
  }
250
255
 
251
- function getId(element) {
256
+ const getId = (element) => {
252
257
  return typeof element === "string" ? element : element.id;
253
- }
258
+ };
259
+ const VIEW_FOLDERS_SEPARATOR = "/";
260
+ const splitViewTitle = (title) => {
261
+ invariant(!title.includes("\n"), "View title cannot contain newlines");
262
+ if (title.includes(VIEW_FOLDERS_SEPARATOR)) {
263
+ const segments = title.split(VIEW_FOLDERS_SEPARATOR).map((s) => s.trim()).filter((s) => s.length > 0);
264
+ if (i$2(segments, 1)) {
265
+ return segments;
266
+ }
267
+ return [""];
268
+ }
269
+ return [title.trim()];
270
+ };
271
+ const normalizeViewPath = (title) => {
272
+ return splitViewTitle(title).join(VIEW_FOLDERS_SEPARATOR);
273
+ };
274
+ const getViewFolderPath = (title) => {
275
+ const segments = splitViewTitle(title);
276
+ if (!i$2(segments, 2)) {
277
+ return null;
278
+ }
279
+ return segments.slice(0, -1).join(VIEW_FOLDERS_SEPARATOR);
280
+ };
281
+ const extractViewTitleFromPath = (title) => {
282
+ if (!title.includes(VIEW_FOLDERS_SEPARATOR)) {
283
+ return title.trim();
284
+ }
285
+ return splitViewTitle(title).pop() ?? title;
286
+ };
254
287
 
255
288
  class LikeC4DeploymentModel {
256
289
  constructor($model) {
@@ -579,13 +612,13 @@ class RelationshipModel {
579
612
  return `${this.source.id} -> ${this.target.id}`;
580
613
  }
581
614
  get title() {
582
- if (!n$1(this.$relationship.title)) {
615
+ if (!n$2(this.$relationship.title)) {
583
616
  return null;
584
617
  }
585
618
  return this.$relationship.title;
586
619
  }
587
620
  get technology() {
588
- if (!n$1(this.$relationship.technology)) {
621
+ if (!n$2(this.$relationship.technology)) {
589
622
  return null;
590
623
  }
591
624
  return this.$relationship.technology;
@@ -629,7 +662,7 @@ class RelationshipModel {
629
662
  return true;
630
663
  }
631
664
  hasMetadata() {
632
- return !!this.$relationship.metadata && !n$2(this.$relationship.metadata);
665
+ return !!this.$relationship.metadata && !n$3(this.$relationship.metadata);
633
666
  }
634
667
  getMetadata(field) {
635
668
  if (field) {
@@ -851,13 +884,13 @@ class NodeModel {
851
884
  * Check if this node references to logical model element.
852
885
  */
853
886
  hasElement() {
854
- return n$1(this.#node.modelRef);
887
+ return n$2(this.#node.modelRef);
855
888
  }
856
889
  /**
857
890
  * Check if this node references to deployment element (Node or Instance).
858
891
  */
859
892
  hasDeployment() {
860
- return n$1(this.#node.deploymentRef);
893
+ return n$2(this.#node.deploymentRef);
861
894
  }
862
895
  /**
863
896
  * Check if this node references to deployed instance
@@ -891,9 +924,23 @@ class LikeC4ViewModel {
891
924
  #allTags = new DefaultMap((_key) => /* @__PURE__ */ new Set());
892
925
  $view;
893
926
  $model;
894
- constructor(model, view) {
927
+ title;
928
+ /**
929
+ * View folder this view belongs to.
930
+ * If view is top-level, this is the root folder.
931
+ */
932
+ folder;
933
+ /**
934
+ * Path to this view, processed by {@link normalizeViewPath}
935
+ *
936
+ * @example
937
+ * "Group 1/Group 2/View"
938
+ */
939
+ viewPath;
940
+ constructor(model, view, folder) {
895
941
  this.$model = model;
896
942
  this.$view = view;
943
+ this.folder = folder;
897
944
  for (const node of view.nodes) {
898
945
  const el = new NodeModel(this, Object.freeze(node));
899
946
  this.#nodes.set(node.id, el);
@@ -925,6 +972,8 @@ class LikeC4ViewModel {
925
972
  }
926
973
  this.#edges.set(edge.id, edgeModel);
927
974
  }
975
+ this.title = this.$view.title ? extractViewTitleFromPath(this.$view.title) : null;
976
+ this.viewPath = this.$view.title ? normalizeViewPath(this.$view.title) : this.$view.id;
928
977
  }
929
978
  get _type() {
930
979
  return this.$view[_type];
@@ -932,8 +981,38 @@ class LikeC4ViewModel {
932
981
  get id() {
933
982
  return this.$view.id;
934
983
  }
935
- get title() {
936
- return this.$view.title;
984
+ /**
985
+ * Returns title if defined, otherwise returns its {@link id}
986
+ */
987
+ get titleOrId() {
988
+ return this.title ?? this.id;
989
+ }
990
+ /**
991
+ * Returns title if defined, otherwise returns `Untitled`.
992
+ */
993
+ get titleOrUntitled() {
994
+ return this.title ?? "Untitled";
995
+ }
996
+ /**
997
+ * Returns path to this view as an array of groups and this view as the last element
998
+ * If view is top-level, returns only this view.
999
+ *
1000
+ * @example
1001
+ * viewPath = "Group 1/Group 2/View"
1002
+ *
1003
+ * breadcrumbs = [
1004
+ * "Group 1", // folder
1005
+ * "Group 1/Group 2", // folder
1006
+ * "Group 1/Group 2/View" // view
1007
+ * ]
1008
+ */
1009
+ get breadcrumbs() {
1010
+ return memoizeProp(this, "breadcrumbs", () => {
1011
+ if (!this.folder.isRoot) {
1012
+ return [...this.folder.breadcrumbs, this];
1013
+ }
1014
+ return [this];
1015
+ });
937
1016
  }
938
1017
  get description() {
939
1018
  return RichText.memoize(this, this.$view.description);
@@ -1066,7 +1145,7 @@ class LikeC4ViewModel {
1066
1145
  return this.$view[_type] === "element";
1067
1146
  }
1068
1147
  isScopedElementView() {
1069
- return this.$view[_type] === "element" && n$1(this.$view.viewOf);
1148
+ return this.$view[_type] === "element" && n$2(this.$view.viewOf);
1070
1149
  }
1071
1150
  isDeploymentView() {
1072
1151
  return this.$view[_type] === "deployment";
@@ -1076,6 +1155,136 @@ class LikeC4ViewModel {
1076
1155
  }
1077
1156
  }
1078
1157
 
1158
+ class LikeC4ViewsFolder {
1159
+ $model;
1160
+ /**
1161
+ * Path to this view folder, processed by {@link normalizeViewPath}
1162
+ *
1163
+ * @example
1164
+ * "Folder 1/Folder 2/Folder 3"
1165
+ */
1166
+ path;
1167
+ /**
1168
+ * Title of this view folder.
1169
+ *
1170
+ * @example
1171
+ * // title is the last segment of the path
1172
+ * path = "Folder 1/Folder 2/Folder 3"
1173
+ * title = "Folder 3"
1174
+ */
1175
+ title;
1176
+ /**
1177
+ * Whether this is the root view folder.
1178
+ *
1179
+ * !NOTE
1180
+ * Root folder is special folder with an empty path and used only for internal purposes. \
1181
+ * It is not visible to the user and should not be used in the code.
1182
+ */
1183
+ isRoot;
1184
+ parentPath;
1185
+ defaultViewId;
1186
+ constructor($model, path, defaultViewId) {
1187
+ this.$model = $model;
1188
+ this.path = path.join("/");
1189
+ this.isRoot = this.path === "";
1190
+ this.title = a(path);
1191
+ if (this.isRoot) {
1192
+ this.parentPath = void 0;
1193
+ } else {
1194
+ this.parentPath = path.slice(0, -1).join("/");
1195
+ }
1196
+ this.defaultViewId = defaultViewId;
1197
+ }
1198
+ /**
1199
+ * Default view of this view folder.\
1200
+ * It is for the case when there is a view at the same path as this folder.\
1201
+ * (if there are multiple views at the same path, the first one is chosen)
1202
+ *
1203
+ * @example
1204
+ * ```
1205
+ * // Assume the following views exist:
1206
+ * const views = [
1207
+ * "Folder 1/ Folder 2 / View",
1208
+ * "Folder 1/ Folder 2 / View / Subview",
1209
+ * ]
1210
+ * ```
1211
+ * Group with path `Folder 1/ Folder 2 / View`\
1212
+ * will have default view `Folder 1/ Folder 2 / View`
1213
+ */
1214
+ get defaultView() {
1215
+ if (!this.defaultViewId) {
1216
+ return null;
1217
+ }
1218
+ return this.$model.view(this.defaultViewId);
1219
+ }
1220
+ /**
1221
+ * Returns path to this view folder as an array of ancestors (excluding root) and this view folder as the last element
1222
+ *
1223
+ * @throws Error if this is the root folder.
1224
+ */
1225
+ get breadcrumbs() {
1226
+ invariant(!this.isRoot, "Root view folder has no breadcrumbs");
1227
+ return memoizeProp(this, "breadcrumbs", () => {
1228
+ const parent = this.parent;
1229
+ if (parent) {
1230
+ if (parent.isRoot) {
1231
+ return [parent, this];
1232
+ }
1233
+ return [...parent.breadcrumbs, this];
1234
+ }
1235
+ return [this];
1236
+ });
1237
+ }
1238
+ /**
1239
+ * Returns parent folder
1240
+ *
1241
+ * @throws Error if this is the root folder.
1242
+ */
1243
+ get parent() {
1244
+ invariant(!this.isRoot, "Root view folder has no parent");
1245
+ if (n$3(this.parentPath)) {
1246
+ return null;
1247
+ }
1248
+ return this.$model.viewFolder(this.parentPath);
1249
+ }
1250
+ /**
1251
+ * Returns sorted set of children
1252
+ * - First folders
1253
+ * - Then views
1254
+ */
1255
+ get children() {
1256
+ return this.$model.viewFolderItems(this.path);
1257
+ }
1258
+ /**
1259
+ * Returns sorted array of children folders
1260
+ */
1261
+ get folders() {
1262
+ return memoizeProp(this, "folders", () => {
1263
+ const folders = [];
1264
+ for (const child of this.children) {
1265
+ if (child instanceof LikeC4ViewsFolder) {
1266
+ folders.push(child);
1267
+ }
1268
+ }
1269
+ return folders;
1270
+ });
1271
+ }
1272
+ /**
1273
+ * Returns all views in this view folder.
1274
+ */
1275
+ get views() {
1276
+ return memoizeProp(this, "views", () => {
1277
+ const views = [];
1278
+ for (const child of this.children) {
1279
+ if (child instanceof LikeC4ViewModel) {
1280
+ views.push(child);
1281
+ }
1282
+ }
1283
+ return views;
1284
+ });
1285
+ }
1286
+ }
1287
+
1079
1288
  class LikeC4Model {
1080
1289
  /**
1081
1290
  * Don't use in runtime, only for type inference
@@ -1095,6 +1304,11 @@ class LikeC4Model {
1095
1304
  // Relationships inside the element, among descendants
1096
1305
  _internal = new DefaultMap(() => /* @__PURE__ */ new Set());
1097
1306
  _views = /* @__PURE__ */ new Map();
1307
+ _rootViewFolder;
1308
+ _viewFolders = /* @__PURE__ */ new Map();
1309
+ _viewFolderItems = new DefaultMap(
1310
+ () => /* @__PURE__ */ new Set()
1311
+ );
1098
1312
  _allTags = new DefaultMap(
1099
1313
  () => /* @__PURE__ */ new Set()
1100
1314
  );
@@ -1169,17 +1383,56 @@ class LikeC4Model {
1169
1383
  }
1170
1384
  this.deployment = new LikeC4DeploymentModel(this);
1171
1385
  if (isOnStage($data, "computed") || isOnStage($data, "layouted")) {
1386
+ const compare = compareNaturalHierarchically(VIEW_FOLDERS_SEPARATOR);
1172
1387
  const views = C(
1173
1388
  r($data.views),
1174
- m((a, b) => compareNatural(a.title ?? "untitled", b.title ?? "untitled"))
1389
+ m$2((view) => ({
1390
+ view,
1391
+ path: normalizeViewPath(view.title ?? view.id),
1392
+ folderPath: view.title && getViewFolderPath(view.title) || ""
1393
+ })),
1394
+ // Sort hierarchically by groups, but keep same order within groups
1395
+ m((a, b) => compare(a.folderPath, b.folderPath))
1175
1396
  );
1176
- for (const view of views) {
1177
- const vm = new LikeC4ViewModel(this, view);
1397
+ const getOrCreateFolder = (path) => {
1398
+ let folder = this._viewFolders.get(path);
1399
+ if (!folder) {
1400
+ const segments = i(path, VIEW_FOLDERS_SEPARATOR);
1401
+ invariant(i$2(segments, 1), `View group path "${path}" must have at least one element`);
1402
+ let defaultView;
1403
+ if (path === "") {
1404
+ defaultView = views.find((view) => view.view.id === "index");
1405
+ } else {
1406
+ defaultView = views.find((view) => view.path === path);
1407
+ }
1408
+ folder = new LikeC4ViewsFolder(this, segments, defaultView?.view.id);
1409
+ this._viewFolders.set(path, folder);
1410
+ }
1411
+ return folder;
1412
+ };
1413
+ this._rootViewFolder = getOrCreateFolder("");
1414
+ for (const { folderPath } of views) {
1415
+ if (this._viewFolders.has(folderPath)) {
1416
+ continue;
1417
+ }
1418
+ i(folderPath, VIEW_FOLDERS_SEPARATOR).reduce((parent, segment) => {
1419
+ const path = [...parent, segment];
1420
+ const folder = getOrCreateFolder(path.join(VIEW_FOLDERS_SEPARATOR));
1421
+ this._viewFolderItems.get(parent.join(VIEW_FOLDERS_SEPARATOR)).add(folder);
1422
+ return path;
1423
+ }, []);
1424
+ }
1425
+ for (const { view, folderPath } of views) {
1426
+ const vm = new LikeC4ViewModel(this, view, getOrCreateFolder(folderPath));
1427
+ this._viewFolderItems.get(folderPath).add(vm);
1178
1428
  this._views.set(view.id, vm);
1179
1429
  for (const tag of vm.tags) {
1180
1430
  this._allTags.get(tag).add(vm);
1181
1431
  }
1182
1432
  }
1433
+ } else {
1434
+ this._rootViewFolder = new LikeC4ViewsFolder(this, [""], void 0);
1435
+ this._viewFolders.set(this._rootViewFolder.path, this._rootViewFolder);
1183
1436
  }
1184
1437
  }
1185
1438
  /**
@@ -1250,6 +1503,21 @@ class LikeC4Model {
1250
1503
  }
1251
1504
  }));
1252
1505
  }
1506
+ /**
1507
+ * Returns the element with the given FQN.
1508
+ *
1509
+ * @throws Error if element is not found\
1510
+ * Use {@link findElement} if you don't want to throw an error
1511
+ *
1512
+ * @note Method is type-safe for typed model
1513
+
1514
+ * @example
1515
+ * model.element('cloud.frontend')
1516
+ * // or object with id property of scalar.Fqn
1517
+ * model.element({
1518
+ * id: 'dashboard',
1519
+ * })
1520
+ */
1253
1521
  element(el) {
1254
1522
  if (el instanceof ElementModel) {
1255
1523
  return el;
@@ -1257,6 +1525,15 @@ class LikeC4Model {
1257
1525
  const id = getId(el);
1258
1526
  return nonNullable(this._elements.get(id), `Element ${id} not found`);
1259
1527
  }
1528
+ /**
1529
+ * Returns the element with the given FQN.
1530
+ *
1531
+ * @returns Element if found, null otherwise
1532
+ * @note Method is not type-safe as {@link element}
1533
+ *
1534
+ * @example
1535
+ * model.findElement('cloud.frontend')
1536
+ */
1260
1537
  findElement(el) {
1261
1538
  return this._elements.get(getId(el)) ?? null;
1262
1539
  }
@@ -1307,14 +1584,62 @@ class LikeC4Model {
1307
1584
  }
1308
1585
  /**
1309
1586
  * Returns a specific view by its ID.
1587
+ * @note Method is type-safe for typed model
1588
+ * @throws Error if view is not found\
1589
+ * Use {@link findView} if you don't want to throw an error
1590
+ *
1591
+ * @example
1592
+ * model.view('index')
1593
+ * // or object with id property of scalar.ViewId
1594
+ * model.view({
1595
+ * id: 'index',
1596
+ * })
1310
1597
  */
1311
1598
  view(viewId) {
1312
1599
  const id = getId(viewId);
1313
1600
  return nonNullable(this._views.get(id), `View ${id} not found`);
1314
1601
  }
1602
+ /**
1603
+ * Returns a specific view by its ID.
1604
+ * @note Method is not type-safe as {@link view}
1605
+ *
1606
+ * @example
1607
+ * model.findView('index')
1608
+ */
1315
1609
  findView(viewId) {
1316
1610
  return this._views.get(viewId) ?? null;
1317
1611
  }
1612
+ /**
1613
+ * Returns a view folder by its path.
1614
+ * Path is extracted from the view title, e.g. "Group 1/Group 2/View" -> "Group 1/Group 2"
1615
+ * @throws Error if view folder is not found.
1616
+ */
1617
+ viewFolder(path) {
1618
+ return nonNullable(this._viewFolders.get(path), `View folder ${path} not found`);
1619
+ }
1620
+ /**
1621
+ * Root folder is a special one with an empty path and used only for internal purposes.
1622
+ * It is not visible to the user and should be used only to get top-level folders and views.
1623
+ */
1624
+ get rootViewFolder() {
1625
+ return this._rootViewFolder;
1626
+ }
1627
+ /**
1628
+ * Whether the model has any view folders.
1629
+ */
1630
+ get hasViewFolders() {
1631
+ return this._viewFolders.size > 1;
1632
+ }
1633
+ /**
1634
+ * Returns all children of a view folder.
1635
+ * Path is extracted from the view title, e.g. "Group 1/Group 2/View" -> "Group 1/Group 2"
1636
+ *
1637
+ * @throws Error if view folder is not found.
1638
+ */
1639
+ viewFolderItems(path) {
1640
+ invariant(this._viewFolders.has(path), `View folder ${path} not found`);
1641
+ return this._viewFolderItems.get(path);
1642
+ }
1318
1643
  /**
1319
1644
  * Returns the parent element of given element.
1320
1645
  * @see ancestors
@@ -1403,7 +1728,8 @@ class LikeC4Model {
1403
1728
  return;
1404
1729
  }
1405
1730
  /**
1406
- * Returns all tags used in the model, sorted alphabetically.
1731
+ * Returns array of all tags used in the model, sorted naturally.\
1732
+ * Use {@link specification.tags} to get all defined tags
1407
1733
  */
1408
1734
  get tags() {
1409
1735
  return memoizeProp(this, "tags", () => m([...this._allTags.keys()], compareNatural));
@@ -1420,7 +1746,7 @@ class LikeC4Model {
1420
1746
  tagged
1421
1747
  })),
1422
1748
  m((a, b) => compareNatural(a.tag, b.tag)),
1423
- a(
1749
+ a$1(
1424
1750
  [p("count"), "desc"]
1425
1751
  )
1426
1752
  ));
@@ -1636,4 +1962,4 @@ function isEdgeModel(edge) {
1636
1962
  return edge instanceof EdgeModel;
1637
1963
  }
1638
1964
 
1639
- export { ElementModel as E, LikeC4Model as L, NodeModel as N, RelationshipModel as R, LikeC4DeploymentModel as a, EdgeModel as b, LikeC4ViewModel as c, isDeployedInstanceModel as d, isDeploymentElementModel as e, isNestedElementOfDeployedInstanceModel as f, isDeploymentRelationModel as g, isRelationModel as h, isDeploymentNodeModel as i, isElementModel as j, isLikeC4ViewModel as k, isNodeModel as l, isEdgeModel as m, a as n, r, t };
1965
+ export { ElementModel as E, LikeC4Model as L, NodeModel as N, RelationshipModel as R, VIEW_FOLDERS_SEPARATOR as V, LikeC4DeploymentModel as a, EdgeModel as b, LikeC4ViewModel as c, LikeC4ViewsFolder as d, extractViewTitleFromPath as e, isDeployedInstanceModel as f, getViewFolderPath as g, isDeploymentElementModel as h, isDeploymentNodeModel as i, isNestedElementOfDeployedInstanceModel as j, isDeploymentRelationModel as k, isRelationModel as l, isElementModel as m, normalizeViewPath as n, isLikeC4ViewModel as o, isNodeModel as p, isEdgeModel as q, r, a$1 as s, t, a as u };
@@ -1,4 +1,4 @@
1
- import { r as getDefaultExportFromCjs } from './core.C_C-3c1S.mjs';
1
+ import { r as getDefaultExportFromCjs } from './core.DQj9iv_y.mjs';
2
2
 
3
3
  /**
4
4
  * Mnemonist DefaultMap
@@ -1,3 +1,6 @@
1
+ import { k } from '../shared/core.BpJu1RrF.mjs';
2
+ import '../shared/core.DbRvwARP.mjs';
3
+
1
4
  const { min: min$4, max: max$4 } = Math;
2
5
 
3
6
  const limit = (x, low = 0, high = 1) => {
@@ -3628,6 +3631,9 @@ const RelationshipColors = {
3628
3631
  slate
3629
3632
  };
3630
3633
 
3634
+ const CONTRAST_MIN_WITH_FOREGROUND = 60;
3635
+ const CONTRAST_START_TONE_DIFFERENCE = 2;
3636
+ const CONTRAST_STEP_TONE_DIFFERENCE = 1;
3631
3637
  const defaultTheme = {
3632
3638
  elements: ElementColors,
3633
3639
  relationships: RelationshipColors,
@@ -3685,12 +3691,14 @@ const defaultTheme = {
3685
3691
  function computeColorValues(color) {
3686
3692
  if (color.match(/^#([0-9a-f]{3}){1,2}$/i)) {
3687
3693
  const colors = generateColors(color);
3694
+ const fillColor = colors[6];
3695
+ const contrastedColors = getContrastedColorsAPCA(fillColor);
3688
3696
  return {
3689
3697
  elements: {
3690
- fill: colors[6],
3698
+ fill: fillColor,
3691
3699
  stroke: colors[7],
3692
- hiContrast: colors[0],
3693
- loContrast: colors[1]
3700
+ hiContrast: contrastedColors[0],
3701
+ loContrast: contrastedColors[1]
3694
3702
  },
3695
3703
  relationships: {
3696
3704
  lineColor: colors[4],
@@ -3705,5 +3713,27 @@ function computeColorValues(color) {
3705
3713
  };
3706
3714
  }
3707
3715
  }
3716
+ function getContrastedColorsAPCA(refColor) {
3717
+ const refColorChroma = chroma(refColor);
3718
+ let lightColorRgb = refColorChroma.brighten(CONTRAST_START_TONE_DIFFERENCE);
3719
+ let darkColorRgb = refColorChroma.darken(CONTRAST_START_TONE_DIFFERENCE);
3720
+ let previousLight;
3721
+ let previousDark;
3722
+ let contrastWithLight;
3723
+ let contrastWithDark;
3724
+ do {
3725
+ previousLight = lightColorRgb;
3726
+ previousDark = darkColorRgb;
3727
+ lightColorRgb = lightColorRgb.brighten(CONTRAST_STEP_TONE_DIFFERENCE);
3728
+ darkColorRgb = darkColorRgb.darken(CONTRAST_STEP_TONE_DIFFERENCE);
3729
+ contrastWithLight = chroma.contrastAPCA(refColorChroma, lightColorRgb);
3730
+ contrastWithDark = chroma.contrastAPCA(refColorChroma, darkColorRgb);
3731
+ } while (Math.abs(contrastWithLight) < CONTRAST_MIN_WITH_FOREGROUND && Math.abs(contrastWithDark) < CONTRAST_MIN_WITH_FOREGROUND && (!k(lightColorRgb, previousLight) || !k(darkColorRgb, previousDark)));
3732
+ if (Math.abs(contrastWithLight) > Math.abs(contrastWithDark)) {
3733
+ return [lightColorRgb.brighten(0.4).hex(), lightColorRgb.hex()];
3734
+ } else {
3735
+ return [darkColorRgb.darken(0.4).hex(), darkColorRgb.hex()];
3736
+ }
3737
+ }
3708
3738
 
3709
3739
  export { ElementColors, RelationshipColors, computeColorValues, defaultTheme };
@@ -1,8 +1,8 @@
1
1
  import { N as NonEmptyArray } from '../shared/core.DuSLYTG6.mjs';
2
2
  export { C as Coalesce, E as ExclusiveUnion, I as IteratorLike, K as KeysOf, L as Link, b as NTuple, a as NonEmptyReadonlyArray, P as Predicate } from '../shared/core.DuSLYTG6.mjs';
3
3
  export { A as Any, A as AnyAux, a as Aux, E as ExtractOnStage, M as ModelStage, S as SpecAux, U as Unknown, b as UnknownComputed, c as UnknownLayouted, d as UnknownParsed, e as _stage, f as _type, _ as aux, i as isOnStage } from '../shared/core.DAj3LuFH.mjs';
4
- import { aP as ViewManualLayout, aK as AutoLayoutDirection } from '../shared/core.IWLie-Ma.mjs';
5
- export { W as AbstractRelationship, ad as AndOperator, aE as AnyExcludePredicate, aD as AnyIncludePredicate, aq as AnyView, aF as AnyViewRuleStyle, z as AuxFromDump, A as AuxFromLikeC4ModelData, B as BBox, aS as BaseParsedViewProperties, aR as BaseViewProperties, aZ as ComputedDeploymentView, a_ as ComputedDynamicView, aX as ComputedEdge, aY as ComputedElementView, C as ComputedLikeC4ModelData, aW as ComputedNode, ao as ComputedView, U as DefaultArrowType, I as DefaultElementShape, T as DefaultLineStyle, K as DefaultPaddingSize, V as DefaultRelationshipColor, J as DefaultShapeSize, N as DefaultTextSize, H as DefaultThemeColor, q as DeployedInstance, r as DeploymentElement, s as DeploymentElementRef, D as DeploymentElementStyle, p as DeploymentNode, w as DeploymentRelation, v as DeploymentRelationship, b5 as DeploymentViewExcludePredicate, b4 as DeploymentViewIncludePredicate, b6 as DeploymentViewPredicate, b8 as DeploymentViewRule, b7 as DeploymentViewRuleStyle, b0 as DiagramEdge, a$ as DiagramNode, am as DiagramView, be as DynamicViewIncludeRule, bb as DynamicViewParallelSteps, bf as DynamicViewRule, ba as DynamicViewStep, bd as DynamicViewStepOrParallel, Q as Element, _ as ElementSpecification, O as ElementStyle, bj as ElementViewExcludePredicate, bi as ElementViewIncludePredicate, bk as ElementViewPredicate, bo as ElementViewRule, bl as ElementViewRuleGroup, bn as ElementViewRuleStyle, a3 as EqualOperator, E as Expression, ai as Filterable, F as FqnExpr, f as FqnRef, j as GlobalDynamicPredicates, G as GlobalPredicateId, i as GlobalPredicates, k as GlobalStyleID, l as GlobalStyles, a6 as KindEqual, b2 as LayoutedDeploymentView, b3 as LayoutedDynamicView, b1 as LayoutedElementView, L as LayoutedLikeC4ModelData, am as LayoutedView, o as LikeC4ModelData, y as LikeC4ModelDump, al as LikeC4View, e as ModelExpression, M as ModelFqnExpr, m as ModelGlobals, Z as ModelRelation, d as ModelRelationExpr, aT as NodeNotation, ab as NotOperator, aj as OperatorPredicate, af as OrOperator, b9 as ParsedDeploymentView, bg as ParsedDynamicView, bp as ParsedElementView, n as ParsedLikeC4ModelData, al as ParsedView, a8 as Participant, a9 as ParticipantOperator, g as Point, P as PredicateSelector, ap as ProcessedView, h as RectBox, c as RelationExpr, Y as Relationship, a1 as RelationshipSpecification, R as RichText, a as RichTextEmpty, b as RichTextOrEmpty, x as SpecTypesFromDump, a2 as Specification, S as SpecificationDump, a4 as TagEqual, $ as TagSpecification, aO as ViewAutoLayout, ar as ViewOnStage, at as ViewRule, aM as ViewRuleAutoLayout, aI as ViewRuleGlobalPredicateRef, aG as ViewRuleGlobalStyle, au as ViewRulePredicate, aQ as ViewType, aV as ViewWithHash, aU as ViewWithNotation, as as ViewWithType, ah as WhereOperator, X as XYPoint, bc as getParallelStepsPrefix, ae as isAndOperator, aL as isAutoLayoutDirection, ax as isComputedView, u as isDeployedInstance, t as isDeploymentNode, aB as isDeploymentView, an as isDiagramView, aC as isDynamicView, bh as isDynamicViewParallelSteps, ay as isElementView, aA as isExtendsElementView, a7 as isKindEqual, an as isLayoutedView, ac as isNotOperator, ag as isOrOperator, aa as isParticipantOperator, az as isScopedElementView, a0 as isTagColorSpecified, a5 as isTagEqual, aN as isViewRuleAutoLayout, aJ as isViewRuleGlobalPredicateRef, aH as isViewRuleGlobalStyle, bm as isViewRuleGroup, av as isViewRulePredicate, aw as isViewRuleStyle, ak as whereOperatorAsPredicate } from '../shared/core.IWLie-Ma.mjs';
4
+ import { aP as ViewManualLayout, aK as AutoLayoutDirection } from '../shared/core.Ddua-wiI.mjs';
5
+ export { W as AbstractRelationship, ad as AndOperator, aE as AnyExcludePredicate, aD as AnyIncludePredicate, aq as AnyView, aF as AnyViewRuleStyle, z as AuxFromDump, A as AuxFromLikeC4ModelData, B as BBox, aS as BaseParsedViewProperties, aR as BaseViewProperties, aZ as ComputedDeploymentView, a_ as ComputedDynamicView, aX as ComputedEdge, aY as ComputedElementView, C as ComputedLikeC4ModelData, aW as ComputedNode, ao as ComputedView, U as DefaultArrowType, I as DefaultElementShape, T as DefaultLineStyle, K as DefaultPaddingSize, V as DefaultRelationshipColor, J as DefaultShapeSize, N as DefaultTextSize, H as DefaultThemeColor, q as DeployedInstance, r as DeploymentElement, s as DeploymentElementRef, D as DeploymentElementStyle, p as DeploymentNode, w as DeploymentRelation, v as DeploymentRelationship, b5 as DeploymentViewExcludePredicate, b4 as DeploymentViewIncludePredicate, b6 as DeploymentViewPredicate, b8 as DeploymentViewRule, b7 as DeploymentViewRuleStyle, b0 as DiagramEdge, a$ as DiagramNode, am as DiagramView, be as DynamicViewIncludeRule, bb as DynamicViewParallelSteps, bf as DynamicViewRule, ba as DynamicViewStep, bd as DynamicViewStepOrParallel, Q as Element, _ as ElementSpecification, O as ElementStyle, bj as ElementViewExcludePredicate, bi as ElementViewIncludePredicate, bk as ElementViewPredicate, bo as ElementViewRule, bl as ElementViewRuleGroup, bn as ElementViewRuleStyle, a3 as EqualOperator, E as Expression, ai as Filterable, F as FqnExpr, f as FqnRef, j as GlobalDynamicPredicates, G as GlobalPredicateId, i as GlobalPredicates, k as GlobalStyleID, l as GlobalStyles, a6 as KindEqual, b2 as LayoutedDeploymentView, b3 as LayoutedDynamicView, b1 as LayoutedElementView, L as LayoutedLikeC4ModelData, am as LayoutedView, o as LikeC4ModelData, y as LikeC4ModelDump, al as LikeC4View, e as ModelExpression, M as ModelFqnExpr, m as ModelGlobals, Z as ModelRelation, d as ModelRelationExpr, aT as NodeNotation, ab as NotOperator, aj as OperatorPredicate, af as OrOperator, b9 as ParsedDeploymentView, bg as ParsedDynamicView, bp as ParsedElementView, n as ParsedLikeC4ModelData, al as ParsedView, a8 as Participant, a9 as ParticipantOperator, g as Point, P as PredicateSelector, ap as ProcessedView, h as RectBox, c as RelationExpr, Y as Relationship, a1 as RelationshipSpecification, R as RichText, a as RichTextEmpty, b as RichTextOrEmpty, x as SpecTypesFromDump, a2 as Specification, S as SpecificationDump, a4 as TagEqual, $ as TagSpecification, aO as ViewAutoLayout, ar as ViewOnStage, at as ViewRule, aM as ViewRuleAutoLayout, aI as ViewRuleGlobalPredicateRef, aG as ViewRuleGlobalStyle, au as ViewRulePredicate, aQ as ViewType, aV as ViewWithHash, aU as ViewWithNotation, as as ViewWithType, ah as WhereOperator, X as XYPoint, bc as getParallelStepsPrefix, ae as isAndOperator, aL as isAutoLayoutDirection, ax as isComputedView, u as isDeployedInstance, t as isDeploymentNode, aB as isDeploymentView, an as isDiagramView, aC as isDynamicView, bh as isDynamicViewParallelSteps, ay as isElementView, aA as isExtendsElementView, a7 as isKindEqual, an as isLayoutedView, ac as isNotOperator, ag as isOrOperator, aa as isParticipantOperator, az as isScopedElementView, a0 as isTagColorSpecified, a5 as isTagEqual, aN as isViewRuleAutoLayout, aJ as isViewRuleGlobalPredicateRef, aH as isViewRuleGlobalStyle, bm as isViewRuleGroup, av as isViewRulePredicate, aw as isViewRuleStyle, ak as whereOperatorAsPredicate } from '../shared/core.Ddua-wiI.mjs';
6
6
  import { F as Fqn, D as DeploymentFqn } from '../shared/core.668kXpZq.mjs';
7
7
  export { A as AnyFqn, B as BuiltInIcon, b as DeploymentKind, j as EdgeId, E as ElementKind, e as GlobalFqn, G as GroupElementKind, I as Icon, a as IconUrl, M as MarkdownOrString, N as NodeId, P as ProjectId, d as RelationId, R as RelationKind, c as RelationshipKind, k as StepEdgeId, S as StepEdgeIdLiteral, m as StepEdgeKind, T as Tag, V as ViewId, o as extractStep, f as flattenMarkdownOrString, g as isGlobalFqn, i as isGroupElementKind, n as isStepEdgeId, s as scalar, h as splitGlobalFqn, l as stepEdgeId } from '../shared/core.668kXpZq.mjs';
8
8
  import { b as BorderStyle, c as ElementShape, h as ThemeColor } from '../shared/core.CzdwsPmY.mjs';
@@ -1,5 +1,5 @@
1
- export { D as DeploymentFqn, E as EdgeId, l as Fqn, F as FqnRef, n as GlobalFqn, G as GroupElementKind, N as NodeId, R as RelationId, S as StepEdgeKind, V as ViewId, _ as _stage, a as _type, r as extractStep, k as flattenMarkdownOrString, h as isAndOperator, c as isDeployedInstance, b as isDeploymentNode, o as isGlobalFqn, m as isGroupElementKind, e as isKindEqual, g as isNotOperator, i as isOnStage, j as isOrOperator, f as isParticipantOperator, q as isStepEdgeId, d as isTagEqual, s as splitGlobalFqn, p as stepEdgeId, w as whereOperatorAsPredicate } from '../shared/core.Br7Fab9l.mjs';
2
- export { E as Expression, F as FqnExpr, b as ModelExpression, M as ModelFqnExpr, a as ModelRelationExpr, R as RelationExpr, p as getParallelStepsPrefix, n as isAutoLayoutDirection, d as isComputedView, j as isDeploymentView, e as isDiagramView, k as isDynamicView, q as isDynamicViewParallelSteps, f as isElementView, h as isExtendsElementView, e as isLayoutedView, g as isScopedElementView, o as isViewRuleAutoLayout, m as isViewRuleGlobalPredicateRef, l as isViewRuleGlobalStyle, r as isViewRuleGroup, i as isViewRulePredicate, c as isViewRuleStyle } from '../shared/core.DXRAwwDB.mjs';
1
+ export { D as DeploymentFqn, E as EdgeId, l as Fqn, F as FqnRef, n as GlobalFqn, G as GroupElementKind, N as NodeId, R as RelationId, S as StepEdgeKind, V as ViewId, _ as _stage, a as _type, r as extractStep, k as flattenMarkdownOrString, h as isAndOperator, c as isDeployedInstance, b as isDeploymentNode, o as isGlobalFqn, m as isGroupElementKind, e as isKindEqual, g as isNotOperator, i as isOnStage, j as isOrOperator, f as isParticipantOperator, q as isStepEdgeId, d as isTagEqual, s as splitGlobalFqn, p as stepEdgeId, w as whereOperatorAsPredicate } from '../shared/core.BajmN8zR.mjs';
2
+ export { E as Expression, F as FqnExpr, b as ModelExpression, M as ModelFqnExpr, a as ModelRelationExpr, R as RelationExpr, p as getParallelStepsPrefix, n as isAutoLayoutDirection, d as isComputedView, j as isDeploymentView, e as isDiagramView, k as isDynamicView, q as isDynamicViewParallelSteps, f as isElementView, h as isExtendsElementView, e as isLayoutedView, g as isScopedElementView, o as isViewRuleAutoLayout, m as isViewRuleGlobalPredicateRef, l as isViewRuleGlobalStyle, r as isViewRuleGroup, i as isViewRulePredicate, c as isViewRuleStyle } from '../shared/core.B1ZN723l.mjs';
3
3
  export { B as BBox, a as BorderStyles, E as ElementShapes, R as RectBox, T as ThemeColors, i as isTagColorSpecified, b as isThemeColor } from '../shared/core.CxGKeL-L.mjs';
4
4
  export { f as DefaultArrowType, a as DefaultElementShape, e as DefaultLineStyle, c as DefaultPaddingSize, g as DefaultRelationshipColor, b as DefaultShapeSize, d as DefaultTextSize, D as DefaultThemeColor, R as RichText } from '../shared/core.D56nCp7e.mjs';
5
5
  import '../shared/core.zLlww-07.mjs';