smbls 3.6.3 → 3.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -197,4 +197,48 @@ See the [@symbo.ls/cli](../cli/) package for the full command reference.
197
197
 
198
198
  ## Documentation
199
199
 
200
+ ## Define System
201
+
202
+ The define system (`context.define` and `element.define`) maps special keys to handler functions. When a key with a matching define handler appears on an element, `throughInitialDefine` calls the handler instead of treating the key as a child or prop.
203
+
204
+ ### Built-in define handlers (in `defaultDefine`)
205
+
206
+ | Key | Purpose |
207
+ |-----|---------|
208
+ | `routes` | Route definitions — passed through as-is |
209
+ | `metadata` | SEO metadata — resolves and applies `<title>` and `<meta>` tags via helmet |
210
+ | `$router` | Router content — wraps params in a fragment and calls `el.set()` |
211
+
212
+ ### Collection define handlers (deprecated in v3)
213
+
214
+ The following collection define handlers existed in v2 but are **deprecated in v3**:
215
+
216
+ | Key (deprecated) | Data source | v3 replacement |
217
+ |-------------------|-------------|----------------|
218
+ | `$collection` | Direct data array/object | Use `children` + `childExtends` |
219
+ | `$propsCollection` | `element.props` as data source | Use `children: ({ props }) => props.items` |
220
+ | `$stateCollection` | `element.state` as data source | Use `children: ({ state }) => state.items` |
221
+ | `$setCollection` | Uses `set()` to update content | Use `content` or `children` |
222
+
223
+ Some older projects still use these handlers via project-level `context.define`. The framework's propertization layer (`@domql/utils/props.js`) is define-aware to avoid moving these keys into `props` when define handlers are present.
224
+
225
+ > **Lesson learned:** The `$` prefix overlaps between css-in-props selectors and define handlers. The propertization in `props.js` checks for define handlers before applying `CSS_SELECTOR_PREFIXES` to prevent define keys from being swallowed into props. This matters for backwards compatibility with v2 projects that still use `$propsCollection` etc.
226
+
227
+ ## Emotion Integration (`prepare.js`)
228
+
229
+ `prepareDesignSystem()` calls `initEmotion()` from `@symbo.ls/emotion/initEmotion.js` to initialize the CSS-in-JS engine. This import must be present for Emotion to work.
230
+
231
+ ```javascript
232
+ import { initEmotion } from '@symbo.ls/emotion/initEmotion.js'
233
+
234
+ export const prepareDesignSystem = (key, context) => {
235
+ const [scratchDesignSystem, emotion, registry] = initEmotion(key, context)
236
+ return [scratchDesignSystem, emotion, registry]
237
+ }
238
+ ```
239
+
240
+ > **Lesson learned:** If the `initEmotion` import is missing or broken, no CSS classes are generated and components render unstyled (Bazaar rendering issue).
241
+
242
+ ## Documentation
243
+
200
244
  For full documentation visit [symbols.app/developers](https://symbols.app/developers).
@@ -1 +1 @@
1
- {"name":"smbls","version":"3.6.1"}
1
+ {"name":"smbls","version":"3.6.5"}
@@ -22,16 +22,7 @@ __export(define_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(define_exports);
24
24
  var import_helmet = require("@symbo.ls/helmet");
25
- var import_utils = require("@domql/utils");
26
- const processCollectionParam = (param, state) => {
27
- if ((0, import_utils.isString)(param)) {
28
- if (param === "state") return state.parse();
29
- return (0, import_utils.getChildStateInKey)(param, state);
30
- }
31
- if ((0, import_utils.isState)(param)) return param.parse();
32
- if ((0, import_utils.isNot)(param)("array", "object")) return null;
33
- return (0, import_utils.deepClone)(param);
34
- };
25
+ var import_fetch = require("@symbo.ls/fetch");
35
26
  const defaultDefine = {
36
27
  routes: (param) => param,
37
28
  metadata: (param, el, state) => {
@@ -41,6 +32,10 @@ const defaultDefine = {
41
32
  const resolved = (0, import_helmet.resolveMetadata)(param, el, state);
42
33
  (0, import_helmet.applyMetadata)(resolved, doc);
43
34
  },
35
+ fetch: (param, el, state, context) => {
36
+ if (!param) return;
37
+ (0, import_fetch.executeFetch)(param, el, state, context);
38
+ },
44
39
  $router: async (param, el) => {
45
40
  if (!param) return;
46
41
  const obj = { tag: "fragment", ...param };
@@ -51,120 +46,5 @@ const defaultDefine = {
51
46
  window.requestAnimationFrame(set);
52
47
  } else await set();
53
48
  return obj;
54
- },
55
- $collection: async (param, el, state) => {
56
- const { __ref: ref } = el;
57
- const {
58
- children: childrenProps,
59
- childrenAs,
60
- childExtends
61
- } = el.props || {};
62
- const children = childrenProps && await (0, import_utils.exec)(childrenProps, el, state);
63
- const childrenAsDefault = childrenAs || "props";
64
- if (children) {
65
- if ((0, import_utils.isObject)(children)) {
66
- param = (0, import_utils.deepClone)(children);
67
- param = Object.keys(param).map((v) => {
68
- const val = param[v];
69
- return (0, import_utils.isObjectLike)(val) ? { ...val, extends: v } : { extends: v, value: val };
70
- });
71
- } else if ((0, import_utils.isArray)(children)) {
72
- param = (0, import_utils.deepClone)(children);
73
- if (childrenAsDefault && childrenAsDefault !== "element") {
74
- param = param.map((v) => ({
75
- ...childExtends && { extends: childExtends },
76
- [childrenAsDefault]: (0, import_utils.isObjectLike)(v) ? v : childrenAsDefault === "state" ? { value: v } : { text: v }
77
- }));
78
- }
79
- } else if ((0, import_utils.isString)(children) || (0, import_utils.isNumber)(children)) {
80
- el.removeContent();
81
- el.content = { text: param };
82
- return;
83
- }
84
- }
85
- if (!param) return;
86
- param = processCollectionParam(param, state);
87
- if (!param) return;
88
- if (ref.__collectionCache) {
89
- const equals = JSON.stringify(param) === JSON.stringify(ref.__collectionCache);
90
- if (equals) {
91
- ref.__noCollectionDifference = true;
92
- return;
93
- }
94
- ref.__collectionCache = (0, import_utils.deepClone)(param);
95
- delete ref.__noCollectionDifference;
96
- } else {
97
- ref.__collectionCache = (0, import_utils.deepClone)(param);
98
- }
99
- const obj = { tag: "fragment", ignoreChildProps: true, childProps: el.props && el.props.childProps };
100
- for (const key in param) {
101
- const value = param[key];
102
- if (value) obj[key] = (0, import_utils.isObjectLike)(value) ? value : { value };
103
- }
104
- el.removeContent();
105
- el.content = obj;
106
- },
107
- $setCollection: async (param, el, state) => {
108
- if (!param) return;
109
- param = processCollectionParam(param, state);
110
- if (!param) return;
111
- const data = ((0, import_utils.isArray)(param) ? param : (0, import_utils.isObject)(param) ? Object.values(param) : []).map((item) => !(0, import_utils.isObjectLike)(item) ? { value: item } : item);
112
- if (data.length) {
113
- const t = setTimeout(() => {
114
- el.set({ tag: "fragment", ...data }, { preventDefineUpdate: "$setCollection" });
115
- clearTimeout(t);
116
- });
117
- }
118
- return data;
119
- },
120
- $stateCollection: async (param, el, state) => {
121
- const { children, childrenAs } = el.props || {};
122
- if (!param || children || childrenAs) return;
123
- param = processCollectionParam(param, state);
124
- if (!param) return;
125
- const { __ref: ref } = el;
126
- if (ref.__stateCollectionCache) {
127
- const equals = JSON.stringify(param) === JSON.stringify(ref.__stateCollectionCache);
128
- if (equals) {
129
- ref.__noCollectionDifference = true;
130
- return;
131
- }
132
- ref.__stateCollectionCache = (0, import_utils.deepClone)(param);
133
- delete ref.__noCollectionDifference;
134
- } else {
135
- ref.__stateCollectionCache = (0, import_utils.deepClone)(param);
136
- }
137
- const obj = { tag: "fragment", ignoreChildProps: true, childProps: el.props && el.props.childProps };
138
- for (const key in param) {
139
- const value = param[key];
140
- if (value) obj[key] = { state: (0, import_utils.isObjectLike)(value) ? value : { value } };
141
- }
142
- el.removeContent();
143
- el.content = obj;
144
- },
145
- $propsCollection: async (param, el, state) => {
146
- const { children, childrenAs } = el.props || {};
147
- if (!param || children || childrenAs) return;
148
- param = processCollectionParam(param, state);
149
- if (!param) return;
150
- const { __ref: ref } = el;
151
- if (ref.__propsCollectionCache) {
152
- const equals = JSON.stringify(param) === JSON.stringify(ref.__propsCollectionCache);
153
- if (equals) {
154
- ref.__noCollectionDifference = true;
155
- return;
156
- }
157
- ref.__propsCollectionCache = (0, import_utils.deepClone)(param);
158
- delete ref.__noCollectionDifference;
159
- } else {
160
- ref.__propsCollectionCache = (0, import_utils.deepClone)(param);
161
- }
162
- const obj = { tag: "fragment", ignoreChildProps: true, childProps: el.props && el.props.childProps };
163
- for (const key in param) {
164
- const value = param[key];
165
- if (value) obj[key] = { props: (0, import_utils.isObjectLike)(value) ? value : { value } };
166
- }
167
- el.removeContent();
168
- el.content = obj;
169
49
  }
170
50
  };
@@ -23,11 +23,74 @@ __export(fetchOnCreate_exports, {
23
23
  });
24
24
  module.exports = __toCommonJS(fetchOnCreate_exports);
25
25
  var import_utils = require("@domql/utils");
26
- var import_fetch = require("@symbo.ls/fetch");
26
+ const IS_DEVELOPMENT = import_utils.window && import_utils.window.location ? import_utils.window.location.host.includes("dev.") : (0, import_utils.isDevelopment)();
27
+ const SERVER_URL = IS_DEVELOPMENT ? "http://localhost:8080/get" : "https://api.symbols.app/get";
28
+ const defaultOptions = {
29
+ endpoint: SERVER_URL
30
+ };
31
+ const fetchRemote = async (key, options = defaultOptions) => {
32
+ const baseUrl = options.endpoint || SERVER_URL;
33
+ const route = options.serviceRoute ? (0, import_utils.isArray)(options.serviceRoute) ? options.serviceRoute.map((v) => v.toLowerCase() + "=true").join("&") : options.serviceRoute : "";
34
+ let response;
35
+ try {
36
+ response = await globalThis.fetch(baseUrl + "/?" + route, {
37
+ method: "GET",
38
+ headers: {
39
+ "Content-Type": "application/json",
40
+ "X-AppKey": key,
41
+ "X-Metadata": options.metadata
42
+ }
43
+ });
44
+ return await response.json();
45
+ } catch (e) {
46
+ if ((0, import_utils.isFunction)(options.onError)) return options.onError(e);
47
+ else console.error(e);
48
+ }
49
+ };
50
+ const fetchProject = async (key, options) => {
51
+ const { editor } = options;
52
+ if (editor && editor.remote) {
53
+ const data = await fetchRemote(key, editor);
54
+ const evalData = IS_DEVELOPMENT || options.isDevelopment ? (0, import_utils.deepDestringifyFunctions)(data) : (0, import_utils.deepDestringifyFunctions)(data.releases[0]);
55
+ if (editor.serviceRoute) {
56
+ if ((0, import_utils.isArray)(editor.serviceRoute)) {
57
+ editor.serviceRoute.forEach((route) => {
58
+ (0, import_utils.overwriteDeep)(options[route], evalData[route.toLowerCase()]);
59
+ });
60
+ } else {
61
+ (0, import_utils.overwriteDeep)(options[editor.serviceRoute], evalData);
62
+ }
63
+ } else {
64
+ ;
65
+ [
66
+ "state",
67
+ "designSystem",
68
+ "components",
69
+ "snippets",
70
+ "pages",
71
+ "utils",
72
+ "files",
73
+ "packages",
74
+ "functions"
75
+ ].forEach((key2) => {
76
+ (0, import_utils.overwriteDeep)(options[key2], evalData[key2.toLowerCase()]);
77
+ });
78
+ }
79
+ }
80
+ return options;
81
+ };
82
+ const fetchProjectAsync = async (key, options, callback) => {
83
+ const { editor } = options;
84
+ if (editor && editor.remote) {
85
+ const data = await fetchRemote(key, editor);
86
+ const evalData = IS_DEVELOPMENT || options.isDevelopment ? (0, import_utils.deepDestringifyFunctions)(data) : (0, import_utils.deepDestringifyFunctions)(data.releases[0]);
87
+ callback(evalData);
88
+ }
89
+ };
27
90
  const fetchSync = async (key, options) => {
28
91
  if (key && options.editor) {
29
92
  try {
30
- if (!options.editor.async) await (0, import_fetch.fetchProject)(key, options);
93
+ if (!options.editor.async) await fetchProject(key, options);
31
94
  } catch (e) {
32
95
  console.error(e);
33
96
  }
@@ -37,7 +100,7 @@ const fetchAsync = (app, key, options, callback) => {
37
100
  if (key && options.editor) {
38
101
  try {
39
102
  if (options.editor.async) {
40
- (0, import_fetch.fetchProjectAsync)(key, options, callback || ((data) => {
103
+ fetchProjectAsync(key, options, callback || ((data) => {
41
104
  const designSystem = data.designSystem;
42
105
  if ((0, import_utils.isObject)(designSystem)) {
43
106
  options.utils.init(designSystem);
@@ -20,7 +20,8 @@ var router_exports = {};
20
20
  __export(router_exports, {
21
21
  initRouter: () => initRouter,
22
22
  injectRouterInLinkComponent: () => injectRouterInLinkComponent,
23
- onpopstateRouter: () => onpopstateRouter
23
+ onpopstateRouter: () => onpopstateRouter,
24
+ resolveRouterElement: () => resolveRouterElement
24
25
  });
25
26
  module.exports = __toCommonJS(router_exports);
26
27
  var import_utils = require("@domql/utils");
@@ -31,6 +32,16 @@ const DEFAULT_ROUTING_OPTIONS = {
31
32
  injectRouterInLinkComponent: true,
32
33
  popState: true
33
34
  };
35
+ const resolveRouterElement = (root, path) => {
36
+ if (!path) return root;
37
+ const parts = Array.isArray(path) ? path : path.split(".");
38
+ let el = root;
39
+ for (const part of parts) {
40
+ if (!el || !el[part]) return null;
41
+ el = el[part];
42
+ }
43
+ return el;
44
+ };
34
45
  const initRouter = (element, context) => {
35
46
  if (context.router === false) return;
36
47
  else if (context.router === true) context.router = DEFAULT_ROUTING_OPTIONS;
@@ -40,7 +51,15 @@ const initRouter = (element, context) => {
40
51
  if (!import_utils.window.location) return;
41
52
  const { pathname, search, hash } = import_utils.window.location;
42
53
  const url = pathname + search + hash;
43
- if (el.routes) await (0, import_router.router)(url, el, {}, { initialRender: true });
54
+ let targetEl = el;
55
+ if (routerOptions.customRouterElement) {
56
+ const resolved = resolveRouterElement(el, routerOptions.customRouterElement);
57
+ if (resolved) {
58
+ targetEl = resolved;
59
+ if (el.routes) targetEl.routes = el.routes;
60
+ }
61
+ }
62
+ if (targetEl.routes) await (0, import_router.router)(url, targetEl, {}, { initialRender: true });
44
63
  };
45
64
  const hasRenderRouter = element.on && !(0, import_utils.isUndefined)(element.on.renderRouter) || !(0, import_utils.isUndefined)(element.onRenderRouter);
46
65
  if (routerOptions && routerOptions.initRouter && !hasRenderRouter) {
@@ -65,10 +84,18 @@ const onpopstateRouter = (element, context) => {
65
84
  import_utils.window.onpopstate = async (e) => {
66
85
  const { pathname, search, hash } = import_utils.window.location;
67
86
  const url = pathname + search + hash;
68
- await element.call(
87
+ let targetEl = element;
88
+ if (routerOptions.customRouterElement) {
89
+ const resolved = resolveRouterElement(element, routerOptions.customRouterElement);
90
+ if (resolved) {
91
+ targetEl = resolved;
92
+ if (element.routes) targetEl.routes = element.routes;
93
+ }
94
+ }
95
+ await targetEl.call(
69
96
  "router",
70
97
  url,
71
- element,
98
+ targetEl,
72
99
  {},
73
100
  { pushState: false, scrollToTop: false, level: 0, event: e }
74
101
  );
@@ -1 +1 @@
1
- {"name":"smbls","version":"3.6.1"}
1
+ {"name":"smbls","version":"3.6.5"}
@@ -1,25 +1,5 @@
1
1
  import { resolveMetadata, applyMetadata } from "@symbo.ls/helmet";
2
- import {
3
- isString,
4
- isNumber,
5
- isNot,
6
- isArray,
7
- isObject,
8
- isObjectLike,
9
- isState,
10
- exec,
11
- deepClone,
12
- getChildStateInKey
13
- } from "@domql/utils";
14
- const processCollectionParam = (param, state) => {
15
- if (isString(param)) {
16
- if (param === "state") return state.parse();
17
- return getChildStateInKey(param, state);
18
- }
19
- if (isState(param)) return param.parse();
20
- if (isNot(param)("array", "object")) return null;
21
- return deepClone(param);
22
- };
2
+ import { executeFetch } from "@symbo.ls/fetch";
23
3
  const defaultDefine = {
24
4
  routes: (param) => param,
25
5
  metadata: (param, el, state) => {
@@ -29,6 +9,10 @@ const defaultDefine = {
29
9
  const resolved = resolveMetadata(param, el, state);
30
10
  applyMetadata(resolved, doc);
31
11
  },
12
+ fetch: (param, el, state, context) => {
13
+ if (!param) return;
14
+ executeFetch(param, el, state, context);
15
+ },
32
16
  $router: async (param, el) => {
33
17
  if (!param) return;
34
18
  const obj = { tag: "fragment", ...param };
@@ -39,121 +23,6 @@ const defaultDefine = {
39
23
  window.requestAnimationFrame(set);
40
24
  } else await set();
41
25
  return obj;
42
- },
43
- $collection: async (param, el, state) => {
44
- const { __ref: ref } = el;
45
- const {
46
- children: childrenProps,
47
- childrenAs,
48
- childExtends
49
- } = el.props || {};
50
- const children = childrenProps && await exec(childrenProps, el, state);
51
- const childrenAsDefault = childrenAs || "props";
52
- if (children) {
53
- if (isObject(children)) {
54
- param = deepClone(children);
55
- param = Object.keys(param).map((v) => {
56
- const val = param[v];
57
- return isObjectLike(val) ? { ...val, extends: v } : { extends: v, value: val };
58
- });
59
- } else if (isArray(children)) {
60
- param = deepClone(children);
61
- if (childrenAsDefault && childrenAsDefault !== "element") {
62
- param = param.map((v) => ({
63
- ...childExtends && { extends: childExtends },
64
- [childrenAsDefault]: isObjectLike(v) ? v : childrenAsDefault === "state" ? { value: v } : { text: v }
65
- }));
66
- }
67
- } else if (isString(children) || isNumber(children)) {
68
- el.removeContent();
69
- el.content = { text: param };
70
- return;
71
- }
72
- }
73
- if (!param) return;
74
- param = processCollectionParam(param, state);
75
- if (!param) return;
76
- if (ref.__collectionCache) {
77
- const equals = JSON.stringify(param) === JSON.stringify(ref.__collectionCache);
78
- if (equals) {
79
- ref.__noCollectionDifference = true;
80
- return;
81
- }
82
- ref.__collectionCache = deepClone(param);
83
- delete ref.__noCollectionDifference;
84
- } else {
85
- ref.__collectionCache = deepClone(param);
86
- }
87
- const obj = { tag: "fragment", ignoreChildProps: true, childProps: el.props && el.props.childProps };
88
- for (const key in param) {
89
- const value = param[key];
90
- if (value) obj[key] = isObjectLike(value) ? value : { value };
91
- }
92
- el.removeContent();
93
- el.content = obj;
94
- },
95
- $setCollection: async (param, el, state) => {
96
- if (!param) return;
97
- param = processCollectionParam(param, state);
98
- if (!param) return;
99
- const data = (isArray(param) ? param : isObject(param) ? Object.values(param) : []).map((item) => !isObjectLike(item) ? { value: item } : item);
100
- if (data.length) {
101
- const t = setTimeout(() => {
102
- el.set({ tag: "fragment", ...data }, { preventDefineUpdate: "$setCollection" });
103
- clearTimeout(t);
104
- });
105
- }
106
- return data;
107
- },
108
- $stateCollection: async (param, el, state) => {
109
- const { children, childrenAs } = el.props || {};
110
- if (!param || children || childrenAs) return;
111
- param = processCollectionParam(param, state);
112
- if (!param) return;
113
- const { __ref: ref } = el;
114
- if (ref.__stateCollectionCache) {
115
- const equals = JSON.stringify(param) === JSON.stringify(ref.__stateCollectionCache);
116
- if (equals) {
117
- ref.__noCollectionDifference = true;
118
- return;
119
- }
120
- ref.__stateCollectionCache = deepClone(param);
121
- delete ref.__noCollectionDifference;
122
- } else {
123
- ref.__stateCollectionCache = deepClone(param);
124
- }
125
- const obj = { tag: "fragment", ignoreChildProps: true, childProps: el.props && el.props.childProps };
126
- for (const key in param) {
127
- const value = param[key];
128
- if (value) obj[key] = { state: isObjectLike(value) ? value : { value } };
129
- }
130
- el.removeContent();
131
- el.content = obj;
132
- },
133
- $propsCollection: async (param, el, state) => {
134
- const { children, childrenAs } = el.props || {};
135
- if (!param || children || childrenAs) return;
136
- param = processCollectionParam(param, state);
137
- if (!param) return;
138
- const { __ref: ref } = el;
139
- if (ref.__propsCollectionCache) {
140
- const equals = JSON.stringify(param) === JSON.stringify(ref.__propsCollectionCache);
141
- if (equals) {
142
- ref.__noCollectionDifference = true;
143
- return;
144
- }
145
- ref.__propsCollectionCache = deepClone(param);
146
- delete ref.__noCollectionDifference;
147
- } else {
148
- ref.__propsCollectionCache = deepClone(param);
149
- }
150
- const obj = { tag: "fragment", ignoreChildProps: true, childProps: el.props && el.props.childProps };
151
- for (const key in param) {
152
- const value = param[key];
153
- if (value) obj[key] = { props: isObjectLike(value) ? value : { value } };
154
- }
155
- el.removeContent();
156
- el.content = obj;
157
26
  }
158
27
  };
159
28
  export {
@@ -1,5 +1,68 @@
1
- import { isObject } from "@domql/utils";
2
- import { fetchProject, fetchProjectAsync } from "@symbo.ls/fetch";
1
+ import { isArray, isFunction, isObject, window, isDevelopment, overwriteDeep, deepDestringifyFunctions } from "@domql/utils";
2
+ const IS_DEVELOPMENT = window && window.location ? window.location.host.includes("dev.") : isDevelopment();
3
+ const SERVER_URL = IS_DEVELOPMENT ? "http://localhost:8080/get" : "https://api.symbols.app/get";
4
+ const defaultOptions = {
5
+ endpoint: SERVER_URL
6
+ };
7
+ const fetchRemote = async (key, options = defaultOptions) => {
8
+ const baseUrl = options.endpoint || SERVER_URL;
9
+ const route = options.serviceRoute ? isArray(options.serviceRoute) ? options.serviceRoute.map((v) => v.toLowerCase() + "=true").join("&") : options.serviceRoute : "";
10
+ let response;
11
+ try {
12
+ response = await globalThis.fetch(baseUrl + "/?" + route, {
13
+ method: "GET",
14
+ headers: {
15
+ "Content-Type": "application/json",
16
+ "X-AppKey": key,
17
+ "X-Metadata": options.metadata
18
+ }
19
+ });
20
+ return await response.json();
21
+ } catch (e) {
22
+ if (isFunction(options.onError)) return options.onError(e);
23
+ else console.error(e);
24
+ }
25
+ };
26
+ const fetchProject = async (key, options) => {
27
+ const { editor } = options;
28
+ if (editor && editor.remote) {
29
+ const data = await fetchRemote(key, editor);
30
+ const evalData = IS_DEVELOPMENT || options.isDevelopment ? deepDestringifyFunctions(data) : deepDestringifyFunctions(data.releases[0]);
31
+ if (editor.serviceRoute) {
32
+ if (isArray(editor.serviceRoute)) {
33
+ editor.serviceRoute.forEach((route) => {
34
+ overwriteDeep(options[route], evalData[route.toLowerCase()]);
35
+ });
36
+ } else {
37
+ overwriteDeep(options[editor.serviceRoute], evalData);
38
+ }
39
+ } else {
40
+ ;
41
+ [
42
+ "state",
43
+ "designSystem",
44
+ "components",
45
+ "snippets",
46
+ "pages",
47
+ "utils",
48
+ "files",
49
+ "packages",
50
+ "functions"
51
+ ].forEach((key2) => {
52
+ overwriteDeep(options[key2], evalData[key2.toLowerCase()]);
53
+ });
54
+ }
55
+ }
56
+ return options;
57
+ };
58
+ const fetchProjectAsync = async (key, options, callback) => {
59
+ const { editor } = options;
60
+ if (editor && editor.remote) {
61
+ const data = await fetchRemote(key, editor);
62
+ const evalData = IS_DEVELOPMENT || options.isDevelopment ? deepDestringifyFunctions(data) : deepDestringifyFunctions(data.releases[0]);
63
+ callback(evalData);
64
+ }
65
+ };
3
66
  const fetchSync = async (key, options) => {
4
67
  if (key && options.editor) {
5
68
  try {
@@ -6,6 +6,16 @@ const DEFAULT_ROUTING_OPTIONS = {
6
6
  injectRouterInLinkComponent: true,
7
7
  popState: true
8
8
  };
9
+ const resolveRouterElement = (root, path) => {
10
+ if (!path) return root;
11
+ const parts = Array.isArray(path) ? path : path.split(".");
12
+ let el = root;
13
+ for (const part of parts) {
14
+ if (!el || !el[part]) return null;
15
+ el = el[part];
16
+ }
17
+ return el;
18
+ };
9
19
  const initRouter = (element, context) => {
10
20
  if (context.router === false) return;
11
21
  else if (context.router === true) context.router = DEFAULT_ROUTING_OPTIONS;
@@ -15,7 +25,15 @@ const initRouter = (element, context) => {
15
25
  if (!window.location) return;
16
26
  const { pathname, search, hash } = window.location;
17
27
  const url = pathname + search + hash;
18
- if (el.routes) await defaultRouter(url, el, {}, { initialRender: true });
28
+ let targetEl = el;
29
+ if (routerOptions.customRouterElement) {
30
+ const resolved = resolveRouterElement(el, routerOptions.customRouterElement);
31
+ if (resolved) {
32
+ targetEl = resolved;
33
+ if (el.routes) targetEl.routes = el.routes;
34
+ }
35
+ }
36
+ if (targetEl.routes) await defaultRouter(url, targetEl, {}, { initialRender: true });
19
37
  };
20
38
  const hasRenderRouter = element.on && !isUndefined(element.on.renderRouter) || !isUndefined(element.onRenderRouter);
21
39
  if (routerOptions && routerOptions.initRouter && !hasRenderRouter) {
@@ -40,10 +58,18 @@ const onpopstateRouter = (element, context) => {
40
58
  window.onpopstate = async (e) => {
41
59
  const { pathname, search, hash } = window.location;
42
60
  const url = pathname + search + hash;
43
- await element.call(
61
+ let targetEl = element;
62
+ if (routerOptions.customRouterElement) {
63
+ const resolved = resolveRouterElement(element, routerOptions.customRouterElement);
64
+ if (resolved) {
65
+ targetEl = resolved;
66
+ if (element.routes) targetEl.routes = element.routes;
67
+ }
68
+ }
69
+ await targetEl.call(
44
70
  "router",
45
71
  url,
46
- element,
72
+ targetEl,
47
73
  {},
48
74
  { pushState: false, scrollToTop: false, level: 0, event: e }
49
75
  );
@@ -61,5 +87,6 @@ const injectRouterInLinkComponent = (context, routerOptions) => {
61
87
  export {
62
88
  initRouter,
63
89
  injectRouterInLinkComponent,
64
- onpopstateRouter
90
+ onpopstateRouter,
91
+ resolveRouterElement
65
92
  };