@symbo.ls/utils 3.2.3 → 3.14.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 (122) hide show
  1. package/LICENSE +25 -12
  2. package/README.md +106 -0
  3. package/array.js +162 -0
  4. package/cache.js +7 -0
  5. package/cdn.js +83 -0
  6. package/component.js +25 -0
  7. package/cookie.js +71 -0
  8. package/{src/detectHeight.js → detectHeight.js} +3 -3
  9. package/dist/cjs/array.js +168 -0
  10. package/dist/cjs/browser.js +0 -2
  11. package/dist/cjs/cache.js +26 -0
  12. package/dist/cjs/cdn.js +85 -0
  13. package/dist/cjs/component.js +45 -0
  14. package/dist/cjs/cookie.js +75 -0
  15. package/dist/cjs/date.js +1 -3
  16. package/dist/cjs/detectHeight.js +4 -6
  17. package/dist/cjs/element.js +136 -0
  18. package/dist/cjs/env.js +43 -0
  19. package/dist/cjs/events.js +39 -0
  20. package/dist/cjs/extends.js +434 -0
  21. package/dist/cjs/fibonacci.js +1 -3
  22. package/dist/cjs/files.js +1 -3
  23. package/dist/cjs/function.js +114 -0
  24. package/dist/cjs/globals.js +30 -0
  25. package/dist/cjs/if.js +48 -0
  26. package/dist/cjs/index.js +38 -425
  27. package/dist/cjs/key.js +38 -0
  28. package/dist/cjs/keys.js +201 -0
  29. package/dist/cjs/load.js +5 -7
  30. package/dist/cjs/log.js +34 -0
  31. package/dist/cjs/metadata.js +310 -0
  32. package/dist/cjs/methods.js +599 -0
  33. package/dist/cjs/node.js +35 -0
  34. package/dist/cjs/object.js +634 -0
  35. package/dist/cjs/projectKeys.js +35 -0
  36. package/dist/cjs/props.js +282 -0
  37. package/dist/cjs/scaling.js +5 -169
  38. package/dist/cjs/scope.js +44 -0
  39. package/dist/cjs/sharedLibraries.js +146 -0
  40. package/dist/cjs/state.js +173 -0
  41. package/dist/cjs/string-extra.js +72 -0
  42. package/dist/cjs/string.js +183 -0
  43. package/dist/cjs/style.js +1 -3
  44. package/dist/cjs/tags.js +220 -0
  45. package/dist/cjs/triggerEvent.js +112 -0
  46. package/dist/cjs/types.js +74 -0
  47. package/dist/cjs/update.js +42 -0
  48. package/dist/esm/array.js +148 -0
  49. package/dist/esm/browser.js +14 -0
  50. package/dist/esm/cache.js +6 -0
  51. package/dist/esm/cdn.js +65 -0
  52. package/dist/esm/component.js +25 -0
  53. package/dist/esm/cookie.js +55 -0
  54. package/dist/esm/date.js +11 -0
  55. package/dist/esm/detectHeight.js +42 -0
  56. package/dist/esm/element.js +116 -0
  57. package/dist/esm/env.js +23 -0
  58. package/dist/esm/events.js +19 -0
  59. package/dist/esm/extends.js +414 -0
  60. package/dist/esm/fibonacci.js +10 -0
  61. package/dist/esm/files.js +4 -0
  62. package/dist/esm/function.js +94 -0
  63. package/dist/esm/globals.js +10 -0
  64. package/dist/esm/if.js +28 -0
  65. package/dist/esm/index.js +37 -0
  66. package/dist/esm/key.js +18 -0
  67. package/dist/esm/keys.js +181 -0
  68. package/dist/esm/load.js +151 -0
  69. package/dist/esm/log.js +14 -0
  70. package/dist/esm/metadata.js +290 -0
  71. package/dist/esm/methods.js +569 -0
  72. package/dist/esm/node.js +15 -0
  73. package/dist/esm/object.js +621 -0
  74. package/dist/esm/projectKeys.js +16 -0
  75. package/dist/esm/props.js +262 -0
  76. package/dist/esm/scaling.js +15 -0
  77. package/dist/esm/scope.js +24 -0
  78. package/dist/esm/sharedLibraries.js +126 -0
  79. package/dist/esm/state.js +163 -0
  80. package/dist/esm/string-extra.js +52 -0
  81. package/dist/esm/string.js +163 -0
  82. package/dist/esm/style.js +20 -0
  83. package/dist/esm/tags.js +200 -0
  84. package/dist/esm/triggerEvent.js +92 -0
  85. package/dist/esm/types.js +54 -0
  86. package/dist/esm/update.js +22 -0
  87. package/dist/iife/index.js +5818 -0
  88. package/element.js +148 -0
  89. package/env.js +15 -0
  90. package/events.js +19 -0
  91. package/extends.js +519 -0
  92. package/function.js +145 -0
  93. package/globals.js +8 -0
  94. package/if.js +30 -0
  95. package/index.js +41 -0
  96. package/key.js +20 -0
  97. package/keys.js +176 -0
  98. package/log.js +12 -0
  99. package/metadata.js +392 -0
  100. package/methods.js +636 -0
  101. package/node.js +37 -0
  102. package/object.js +803 -0
  103. package/package.json +38 -22
  104. package/projectKeys.js +30 -0
  105. package/props.js +327 -0
  106. package/{src/scaling.js → scaling.js} +1 -1
  107. package/scope.js +32 -0
  108. package/sharedLibraries.js +184 -0
  109. package/state.js +203 -0
  110. package/{src/index.js → string-extra.js} +11 -17
  111. package/string.js +206 -0
  112. package/tags.js +176 -0
  113. package/triggerEvent.js +102 -0
  114. package/types.js +55 -0
  115. package/update.js +27 -0
  116. package/dist/cjs/package.json +0 -4
  117. /package/{src/browser.js → browser.js} +0 -0
  118. /package/{src/date.js → date.js} +0 -0
  119. /package/{src/fibonacci.js → fibonacci.js} +0 -0
  120. /package/{src/files.js → files.js} +0 -0
  121. /package/{src/load.js → load.js} +0 -0
  122. /package/{src/style.js → style.js} +0 -0
package/package.json CHANGED
@@ -1,31 +1,47 @@
1
1
  {
2
2
  "name": "@symbo.ls/utils",
3
- "version": "3.2.3",
4
- "author": "symbo.ls",
3
+ "version": "3.14.0",
4
+ "license": "CC-BY-NC-4.0",
5
+ "type": "module",
6
+ "module": "./index.js",
7
+ "main": "./index.js",
8
+ "exports": {
9
+ ".": "./index.js",
10
+ "./methods": "./methods.js",
11
+ "./*.js": "./*.js",
12
+ "./*": "./*.js",
13
+ "./package.json": "./package.json"
14
+ },
15
+ "source": "index.js",
5
16
  "files": [
6
- "src",
7
- "dist"
17
+ "dist",
18
+ "*.js"
8
19
  ],
9
- "main": "src/index.js",
10
- "type": "module",
11
- "module": "src/index.js",
12
- "unpkg": "dist/iife/index.js",
13
- "jsdelivr": "dist/iife/index.js",
14
- "exports": "./dist/cjs/index.js",
15
- "source": "src/index.js",
16
- "publishConfig": {},
17
20
  "scripts": {
18
21
  "copy:package:cjs": "cp ../../build/package-cjs.json dist/cjs/package.json",
19
- "build:esm": "npx cross-env NODE_ENV=$NODE_ENV npx esbuild ./src/*.js ./src/**/*.js --target=es2017 --format=esm --outdir=dist/esm",
20
- "build:cjs": "npx cross-env NODE_ENV=$NODE_ENV npx esbuild ./src/*.js ./src/**/*.js --target=node16 --format=cjs --outdir=dist/cjs --bundle",
21
- "build:iife": "npx cross-env NODE_ENV=$NODE_ENV npx esbuild ./src/index.js --target=es2017 --format=iife --outdir=dist/iife --bundle --minify",
22
- "build": "npm run build:cjs",
23
- "prepublish": "rimraf -I dist && npm run build && npm run copy:package:cjs"
22
+ "build:esm": "NODE_ENV=$NODE_ENV esbuild *.js --target=es2020 --format=esm --outdir=dist/esm --define:process.env.NODE_ENV=process.env.NODE_ENV",
23
+ "build:cjs": "NODE_ENV=$NODE_ENV esbuild *.js --target=node18 --format=cjs --outdir=dist/cjs --define:process.env.NODE_ENV=process.env.NODE_ENV",
24
+ "build": "node ../../build/build.js",
25
+ "prepublish": "npm run build && npm run copy:package:cjs",
26
+ "build:iife": "NODE_ENV=$NODE_ENV esbuild index.js --bundle --target=es2020 --format=iife --global-name=DomqlUtils --outfile=dist/iife/index.js --define:process.env.NODE_ENV=process.env.NODE_ENV --external:@symbo.ls/element"
27
+ },
28
+ "gitHead": "9fc1b79b41cdc725ca6b24aec64920a599634681",
29
+ "peerDependencies": {
30
+ "@symbo.ls/fetch": "^3.14.0"
31
+ },
32
+ "peerDependenciesMeta": {
33
+ "@symbo.ls/fetch": {
34
+ "optional": true
35
+ }
24
36
  },
25
- "license": "ISC",
26
- "dependencies": {
27
- "@domql/event": "^3.2.3",
28
- "@domql/utils": "^3.2.3"
37
+ "devDependencies": {
38
+ "@babel/core": "^7.26.0"
29
39
  },
30
- "gitHead": "9fc1b79b41cdc725ca6b24aec64920a599634681"
40
+ "browser": "./index.js",
41
+ "unpkg": "./dist/iife/index.js",
42
+ "jsdelivr": "./dist/iife/index.js",
43
+ "sideEffects": false,
44
+ "publishConfig": {
45
+ "access": "public"
46
+ }
31
47
  }
package/projectKeys.js ADDED
@@ -0,0 +1,30 @@
1
+ // Top-level keys that must never land on `ProjectData.data.*` under any
2
+ // circumstance. Two categories are universal and truly non-negotiable:
3
+ //
4
+ // - JS-level pollution: assigning to `__proto__` / `constructor` /
5
+ // `prototype` can mutate the global prototype chain.
6
+ // - Mongo / server-owned metadata: `_id` / `__v` / `id` / `createdAt` /
7
+ // `updatedAt` / `schema` are allocated or maintained by the server and
8
+ // must be treated as opaque identity. Clients that accidentally send
9
+ // them would either overwrite server state or corrupt the doc.
10
+ //
11
+ // That's all that belongs here. The larger set of "Project-schema-owned"
12
+ // fields (access, visibility, members, subscription, etc.) is derived on
13
+ // the server at runtime from `Project.schema.paths`, not enumerated. See
14
+ // the server's `services/ProjectContentGuard.js` for that derivation.
15
+ // The CLI only needs the universal floor.
16
+
17
+ export const UNIVERSAL_RESERVED_KEYS = new Set([
18
+ '__proto__',
19
+ 'constructor',
20
+ 'prototype',
21
+ '_id',
22
+ '__v',
23
+ 'id',
24
+ 'createdAt',
25
+ 'updatedAt',
26
+ 'schema',
27
+ ])
28
+
29
+ export const isUniversallyReservedKey = (key) =>
30
+ typeof key === 'string' && UNIVERSAL_RESERVED_KEYS.has(key)
package/props.js ADDED
@@ -0,0 +1,327 @@
1
+ 'use strict'
2
+
3
+ import { DOMQ_PROPERTIES, PROPS_METHODS } from './keys.js'
4
+ import { addEventFromProps } from './events.js'
5
+ import { deepClone, deepMerge, exec } from './object.js'
6
+ import { is, isArray, isFunction, isObject, isObjectLike } from './types.js'
7
+ import { lowercaseFirstLetter } from './string.js'
8
+ import { hasHandlerPlugin } from './function.js'
9
+
10
+ const RE_UPPER = /^[A-Z]/
11
+ const RE_DIGITS = /^\d+$/
12
+
13
+ // Characters that mark css-in-props selectors (pseudo-selectors, media queries, etc.)
14
+ // These properties must always live in props so useCssInProps() can process them
15
+ const CSS_SELECTOR_PREFIXES = new Set([':', '@', '[', '*', '+', '~', '&', '>', '$', '-', '.', '!'])
16
+
17
+ const ELEMENT_INDICATOR_KEYS = new Set([
18
+ 'extend', 'props', 'text', 'tag', 'on', 'if', 'childExtend',
19
+ 'children', 'childrenAs', 'state', 'html', 'attr',
20
+ 'define', 'content'
21
+ ])
22
+ const looksLikeElement = (value) => {
23
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false
24
+ for (const k in value) {
25
+ if (ELEMENT_INDICATOR_KEYS.has(k)) return true
26
+ if (RE_UPPER.test(k)) return true
27
+ }
28
+ return false
29
+ }
30
+
31
+ export const createProps = (element, parent, key) => {
32
+ const { props, __ref: ref } = element
33
+ ref.__propsStack = []
34
+ if (props) ref.__initialProps = props
35
+ else return {}
36
+ if (!isObjectLike(props)) {
37
+ ref.__propsStack.push(props)
38
+ return {}
39
+ }
40
+ return { ...props }
41
+ }
42
+
43
+ export function pickupPropsFromElement (obj, opts = {}) {
44
+ const cachedKeys = opts.cachedKeys || []
45
+
46
+ for (const key in obj) {
47
+ const value = obj[key]
48
+
49
+ // Move top-level onXxx handlers directly into on.xxx (v3 style)
50
+ const isOnKey = key.length > 2 && key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && key[2] === key[2].toUpperCase()
51
+ const isEventHandler = isOnKey && (isFunction(value) || (value != null && hasHandlerPlugin(this.context)))
52
+ if (isEventHandler) {
53
+ const eventName = lowercaseFirstLetter(key.slice(2))
54
+ if (obj.on) obj.on[eventName] = value
55
+ delete obj[key]
56
+ continue
57
+ }
58
+
59
+ // childProps is a framework property that must always live in props
60
+ // even though its value may contain uppercase keys (Icon, Hgroup, etc.)
61
+ if (key === 'childProps') {
62
+ obj.props[key] = value
63
+ delete obj[key]
64
+ cachedKeys.push(key)
65
+ continue
66
+ }
67
+
68
+ // Check define handlers - keys with define handlers must stay at root
69
+ // for throughInitialDefine to process them
70
+ const defineValue = this.define?.[key]
71
+ const globalDefineValue = this.context?.define?.[key]
72
+ const hasDefine = isObject(defineValue) || isFunction(defineValue)
73
+ const hasGlobalDefine = isObject(globalDefineValue) || isFunction(globalDefineValue)
74
+ if (hasDefine || hasGlobalDefine) continue
75
+
76
+ // CSS-in-props selectors (:after, :hover, @mobileS, etc.) must always
77
+ // live in props so useCssInProps() can process them via transformersByPrefix
78
+ const firstChar = key.charAt(0)
79
+ if (CSS_SELECTOR_PREFIXES.has(firstChar)) {
80
+ obj.props[key] = value
81
+ delete obj[key]
82
+ cachedKeys.push(key)
83
+ continue
84
+ }
85
+
86
+ const isElement = RE_UPPER.test(key) || RE_DIGITS.test(key) || looksLikeElement(value)
87
+ const isBuiltin = DOMQ_PROPERTIES.has(key)
88
+
89
+ // If it's not a special case, move to props
90
+ // Don't overwrite props already set by local definition (e.g. from childrenAs: 'props')
91
+ if (!isElement && !isBuiltin) {
92
+ if (!(key in obj.props)) obj.props[key] = value
93
+ delete obj[key]
94
+ cachedKeys.push(key)
95
+ }
96
+ }
97
+
98
+ return obj
99
+ }
100
+
101
+ export function pickupElementFromProps (obj = this, opts) {
102
+ const cachedKeys = opts.cachedKeys || []
103
+
104
+ for (const key in obj.props) {
105
+ const value = obj.props[key]
106
+
107
+ // Handle event handlers (functions, or plugin schemas when plugins are active)
108
+ const isEvent = key.length > 2 && key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110
109
+ const isHandler = isFunction(value) || (value != null && hasHandlerPlugin(this.context))
110
+
111
+ if (isEvent && isHandler) {
112
+ addEventFromProps(key, obj)
113
+ delete obj.props[key]
114
+ continue
115
+ }
116
+
117
+ // Skip if key was originally from obj
118
+ if (cachedKeys.includes(key)) continue
119
+
120
+ // childProps must stay in props - it's consumed by inheritParentProps
121
+ if (key === 'childProps') continue
122
+
123
+ // CSS-in-props selectors must stay in props
124
+ const firstChar = key.charAt(0)
125
+ if (CSS_SELECTOR_PREFIXES.has(firstChar)) continue
126
+
127
+ const defineValue = this.define?.[key]
128
+ const globalDefineValue = this.context?.define?.[key]
129
+ const hasDefine = isObject(defineValue) || isFunction(defineValue)
130
+ const hasGlobalDefine = isObject(globalDefineValue) || isFunction(globalDefineValue)
131
+ const isElement = RE_UPPER.test(key) || RE_DIGITS.test(key)
132
+ const isBuiltin = DOMQ_PROPERTIES.has(key)
133
+
134
+ // Move qualifying properties back to obj root
135
+ if (isElement || isBuiltin || hasDefine || hasGlobalDefine) {
136
+ // Don't overwrite if root already has this property explicitly set
137
+ // Exception: null values should override to allow nullifying children from props
138
+ if (obj[key] === undefined || value === null) {
139
+ obj[key] = value
140
+ if (obj.props) delete obj.props[key]
141
+ }
142
+ // If root already has the property (e.g. from extends), keep it in props
143
+ // so functions like `text: ({ props }) => props.text` can still access it
144
+ }
145
+ }
146
+
147
+ return obj
148
+ }
149
+
150
+ // Helper function to maintain compatibility with original propertizeElement
151
+ export function propertizeElement (element = this) {
152
+ const cachedKeys = []
153
+ pickupPropsFromElement.call(this, element, { cachedKeys })
154
+ pickupElementFromProps.call(this, element, { cachedKeys })
155
+ return element
156
+ }
157
+
158
+ export function propertizeUpdate (params = {}) {
159
+ if (!params.on) params.on = {}
160
+ if (!params.props) params.props = {}
161
+ return propertizeElement.call(this, params)
162
+ }
163
+
164
+ export const objectizeStringProperty = propValue => {
165
+ if (is(propValue)('string', 'number')) {
166
+ return { inheritedString: propValue }
167
+ }
168
+ return propValue
169
+ }
170
+
171
+ export const propExists = (prop, stack) => {
172
+ if (!prop || !stack.length) return false
173
+ return stack.includes(prop)
174
+ }
175
+
176
+ export const inheritParentProps = (element, parent) => {
177
+ const { __ref: ref } = element
178
+ const propsStack = ref.__propsStack || []
179
+ const parentProps = parent.props
180
+
181
+ if (!parentProps) return propsStack
182
+
183
+ const matchParentKeyProps = parentProps[element.key]
184
+ const matchParentChildProps = parentProps.childProps || parent.childProps
185
+
186
+ // Order matters: key-specific props should be added after childProps
187
+ const ignoreChildProps = element.props?.ignoreChildProps
188
+ if (matchParentChildProps && !ignoreChildProps) {
189
+ const childProps = objectizeStringProperty(matchParentChildProps)
190
+ propsStack.unshift(childProps)
191
+ }
192
+
193
+ if (matchParentKeyProps) {
194
+ const keyProps = objectizeStringProperty(matchParentKeyProps)
195
+ propsStack.unshift(keyProps)
196
+ }
197
+
198
+ return propsStack
199
+ }
200
+
201
+ export function update (props, options) {
202
+ const element = this.__element
203
+ element.update({ props }, options)
204
+ }
205
+
206
+ // TODO: check bind with promise
207
+ export function setPropsPrototype (element) {
208
+ const methods = { update: update.bind(element.props), __element: element }
209
+ Object.setPrototypeOf(element.props, methods)
210
+ }
211
+
212
+ export const removeDuplicateProps = propsStack => {
213
+ const seen = new Set()
214
+
215
+ return propsStack.filter(prop => {
216
+ if (!prop || PROPS_METHODS.has(prop)) return false
217
+ if (seen.has(prop)) return false
218
+ seen.add(prop)
219
+ return true
220
+ })
221
+ }
222
+
223
+ export const syncProps = (propsStack, element, opts) => {
224
+ element.props = propsStack.reduce((mergedProps, v) => {
225
+ if (PROPS_METHODS.has(v)) return mergedProps
226
+ while (isFunction(v)) v = exec(v, element)
227
+ return deepMerge(mergedProps, deepClone(v, { exclude: PROPS_METHODS }))
228
+ }, {})
229
+ setPropsPrototype(element)
230
+ return element.props
231
+ }
232
+
233
+ export const createPropsStack = (element, parent) => {
234
+ const { props, __ref: ref } = element
235
+
236
+ // Start with parent props
237
+ let propsStack = ref.__propsStack || []
238
+
239
+ // Get parent props
240
+ if (parent?.props) {
241
+ const parentStack = inheritParentProps(element, parent)
242
+ propsStack = [...parentStack]
243
+ }
244
+
245
+ // Add current props
246
+ if (isObject(props)) propsStack.push(props)
247
+ else if (props === 'inherit' && parent?.props) propsStack.push(parent.props)
248
+ else if (props) propsStack.push(props)
249
+
250
+ // Add extends props
251
+ if (isArray(ref.__extendsStack)) {
252
+ for (let i = 0; i < ref.__extendsStack.length; i++) {
253
+ const _extends = ref.__extendsStack[i]
254
+ if (_extends.props && _extends.props !== props) {
255
+ propsStack.push(_extends.props)
256
+ }
257
+ }
258
+ }
259
+
260
+ // Remove duplicates and update reference
261
+ ref.__propsStack = removeDuplicateProps(propsStack)
262
+ return ref.__propsStack
263
+ }
264
+
265
+ export const applyProps = (element, parent) => {
266
+ const { __ref: ref } = element
267
+
268
+ // Create a fresh props stack
269
+ const propsStack = createPropsStack(element, parent)
270
+
271
+ // Update the element
272
+ if (propsStack.length) {
273
+ syncProps(propsStack, element)
274
+ } else {
275
+ ref.__propsStack = []
276
+ element.props = {}
277
+ }
278
+ }
279
+
280
+ export const initProps = function (element, parent, options) {
281
+ const { __ref: ref } = element
282
+
283
+ if (ref.__if) applyProps(element, parent)
284
+ else {
285
+ try {
286
+ applyProps(element, parent)
287
+ } catch (e) {
288
+ if (element.context?.designSystem?.verbose) {
289
+ console.warn('initProps error at', ref.path?.join('.'), e)
290
+ }
291
+ element.props = {}
292
+ ref.__propsStack = []
293
+ }
294
+ }
295
+
296
+ setPropsPrototype(element)
297
+
298
+ return element
299
+ }
300
+
301
+ export const updateProps = (newProps, element, parent) => {
302
+ const { __ref: ref } = element
303
+ const propsStack = ref.__propsStack || []
304
+
305
+ // Create a new array to avoid mutating the original
306
+ let newStack = [...propsStack]
307
+
308
+ // Add parent props first if they exist
309
+ const parentProps = inheritParentProps(element, parent)
310
+ if (parentProps.length) {
311
+ newStack = [...parentProps, ...newStack]
312
+ }
313
+
314
+ // Add new props if they exist
315
+ if (newProps) {
316
+ newStack = [newProps, ...newStack]
317
+ }
318
+
319
+ // Clean up duplicates
320
+ ref.__propsStack = removeDuplicateProps(newStack)
321
+
322
+ if (ref.__propsStack.length) {
323
+ syncProps(ref.__propsStack, element)
324
+ }
325
+
326
+ return element
327
+ }
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- import { isObject, isArray } from '@domql/utils'
3
+ import { isObject, isArray } from './types.js'
4
4
 
5
5
  export const findClosestNumber = (number, arr) => {
6
6
  return (isArray(arr) ? arr : Object.values(arr)).reduce((prev, curr) => {
package/scope.js ADDED
@@ -0,0 +1,32 @@
1
+ 'use strict'
2
+
3
+ // Create scope - shared object across the elements to the own or the nearest parent
4
+ // Prototype chain: own scope → parent scope → ... → root scope → globalScope
5
+ export const createScope = (element, parent) => {
6
+ const { __ref: ref } = element
7
+ const context = element.context || parent.context || ref.root?.context
8
+
9
+ // Ensure globalScope exists on context
10
+ if (context && !context.globalScope) context.globalScope = {}
11
+
12
+ const parentScope = parent.scope || ref.root?.scope
13
+ const globalScope = context?.globalScope
14
+
15
+ if (!element.scope) {
16
+ // No own scope defined — inherit parent scope directly
17
+ if (parentScope) {
18
+ element.scope = parentScope
19
+ } else if (globalScope) {
20
+ // No parent scope either — create root-level scope with globalScope as prototype
21
+ element.scope = Object.create(globalScope)
22
+ } else {
23
+ element.scope = {}
24
+ }
25
+ } else if (typeof element.scope === 'object' && element.scope !== null) {
26
+ // Element defines its own scope — chain prototype to parent scope (or globalScope)
27
+ if (Object.getPrototypeOf(element.scope) === Object.prototype) {
28
+ const proto = parentScope || globalScope
29
+ if (proto) Object.setPrototypeOf(element.scope, proto)
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,184 @@
1
+ 'use strict'
2
+
3
+ import { isObject, isString } from './types.js'
4
+
5
+ const DEFAULT_LIBRARY_OWNER = 'system'
6
+
7
+ const KV_BASE_URL = 'https://smbls-kv.nika-980.workers.dev'
8
+ const API_BASE_URL = 'https://api.symbols.app'
9
+
10
+ /**
11
+ * Normalize any library key format to canonical { owner, key, full } form.
12
+ *
13
+ * Accepts:
14
+ * "system/default" → { owner: "system", key: "default", full: "system/default" }
15
+ * "default" → { owner: "system", key: "default", full: "system/default" }
16
+ * "default.symbo.ls" → { owner: "system", key: "default", full: "system/default" }
17
+ * "myorg/mylib" → { owner: "myorg", key: "mylib", full: "myorg/mylib" }
18
+ */
19
+ export function normalizeLibraryKey (raw) {
20
+ const s = String(raw || '').trim()
21
+ if (!s) return { owner: DEFAULT_LIBRARY_OWNER, key: '', full: '' }
22
+
23
+ let owner = null
24
+ let key = s
25
+
26
+ if (s.includes('/')) {
27
+ const idx = s.indexOf('/')
28
+ owner = s.slice(0, idx)
29
+ key = s.slice(idx + 1)
30
+ }
31
+
32
+ // Strip deprecated .symbo.ls suffix
33
+ key = key.replace(/\.symbo\.ls$/iu, '')
34
+
35
+ if (!owner) owner = DEFAULT_LIBRARY_OWNER
36
+
37
+ const full = key ? `${owner}/${key}` : ''
38
+
39
+ return { owner, key, full }
40
+ }
41
+
42
+ /**
43
+ * Recursively set defaults: copy keys from source into target
44
+ * only where target is missing them. Non-destructive.
45
+ */
46
+ export const deepDefaults = (target, source) => {
47
+ for (const k in source) {
48
+ if (!(k in target)) {
49
+ target[k] = source[k]
50
+ } else if (isObject(target[k]) && isObject(source[k])) {
51
+ deepDefaults(target[k], source[k])
52
+ }
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Merge an array of shared library objects into a context.
58
+ * - designSystem: deep defaults (non-destructive recursive)
59
+ * - other object keys (components, pages, etc.): shallow add missing
60
+ * - new top-level keys: copy from library
61
+ */
62
+ export const mergeSharedLibraries = (context, libraries) => {
63
+ if (!libraries || !libraries.length) return
64
+ for (let i = 0; i < libraries.length; i++) {
65
+ const sharedLib = libraries[i]
66
+ if (!isObject(sharedLib)) continue
67
+ for (const key in sharedLib) {
68
+ if (!Object.prototype.hasOwnProperty.call(sharedLib, key)) continue
69
+ if (isObject(sharedLib[key]) && isObject(context[key])) {
70
+ if (key === 'designSystem') {
71
+ deepDefaults(context[key], sharedLib[key])
72
+ } else {
73
+ for (const k in sharedLib[key]) {
74
+ if (!(k in context[key])) context[key][k] = sharedLib[key][k]
75
+ }
76
+ }
77
+ } else if (!(key in context)) {
78
+ context[key] = sharedLib[key]
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Fetch a single library's data by identifier.
86
+ *
87
+ * @param {string} identifier - "owner/key" format
88
+ * @param {object} [opts]
89
+ * @param {string} [opts.provider='kv'] - 'kv' or 'api'
90
+ * @param {string} [opts.env='production'] - KV environment
91
+ * @param {string} [opts.kvBaseUrl] - Override KV base URL
92
+ * @param {string} [opts.apiBaseUrl] - Override API base URL
93
+ * @returns {Promise<object|null>} Library data or null
94
+ */
95
+ export async function fetchLibraryData (identifier, opts = {}) {
96
+ const provider = opts.provider || 'kv'
97
+
98
+ if (provider === 'api') {
99
+ return fetchLibraryFromApi(identifier, opts)
100
+ }
101
+
102
+ // KV provider (default)
103
+ const result = await fetchLibraryFromKv(identifier, opts)
104
+ if (result) return result
105
+
106
+ // Fallback to API if KV returns nothing
107
+ return fetchLibraryFromApi(identifier, opts)
108
+ }
109
+
110
+ async function fetchLibraryFromKv (identifier, opts = {}) {
111
+ const baseUrl = opts.kvBaseUrl || KV_BASE_URL
112
+ const env = opts.env || 'production'
113
+ const url = `${baseUrl}/kv/${encodeURIComponent(identifier)}?env=${env}`
114
+ try {
115
+ const response = await fetch(url, { method: 'GET' })
116
+ if (!response.ok) return null
117
+ const json = await response.json()
118
+ return json?.value || null
119
+ } catch {
120
+ return null
121
+ }
122
+ }
123
+
124
+ async function fetchLibraryFromApi (identifier, opts = {}) {
125
+ const baseUrl = opts.apiBaseUrl || API_BASE_URL
126
+ const { key } = normalizeLibraryKey(identifier)
127
+ try {
128
+ // Resolve library ID from the available list
129
+ const listUrl = `${baseUrl}/core/projects/libraries/available?search=${encodeURIComponent(key)}&limit=10`
130
+ const listRes = await fetch(listUrl, { method: 'GET' })
131
+ if (!listRes.ok) return null
132
+ const listJson = await listRes.json()
133
+ const items = listJson?.items || listJson?.data || (Array.isArray(listJson) ? listJson : [])
134
+ const match = items.find(lib => {
135
+ const { key: libKey } = normalizeLibraryKey(lib?.key)
136
+ return libKey.toLowerCase() === key.toLowerCase()
137
+ })
138
+ if (!match?.id && !match?._id) return null
139
+ const libId = match.id || match._id
140
+
141
+ // Fetch full library data
142
+ const dataUrl = `${baseUrl}/core/projects/${encodeURIComponent(libId)}/data?branch=main`
143
+ const dataRes = await fetch(dataUrl, { method: 'GET' })
144
+ if (!dataRes.ok) return null
145
+ const dataJson = await dataRes.json()
146
+ return dataJson?.data || dataJson || null
147
+ } catch {
148
+ return null
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Resolve an array that may contain strings and/or objects.
154
+ * Strings are treated as library identifiers and fetched.
155
+ * Objects pass through unchanged.
156
+ *
157
+ * @param {Array<string|object>} libraries - Mixed array
158
+ * @param {object} [opts] - Options passed to fetchLibraryData
159
+ * @returns {Promise<object[]>} Array of resolved library objects
160
+ */
161
+ export async function resolveSharedLibraries (libraries, opts = {}) {
162
+ if (!libraries || !libraries.length) return []
163
+ const resolved = await Promise.all(
164
+ libraries.map(async (entry) => {
165
+ if (isObject(entry)) return entry
166
+ if (isString(entry)) {
167
+ const { full } = normalizeLibraryKey(entry)
168
+ if (!full) return null
169
+ try {
170
+ const data = await fetchLibraryData(full, opts)
171
+ if (!data) {
172
+ console.warn(`[smbls] Shared library "${full}" not found`)
173
+ }
174
+ return data
175
+ } catch (e) {
176
+ console.warn(`[smbls] Failed to fetch shared library "${full}":`, e.message)
177
+ return null
178
+ }
179
+ }
180
+ return null
181
+ })
182
+ )
183
+ return resolved.filter(Boolean)
184
+ }