@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.
- package/LICENSE +25 -12
- package/README.md +106 -0
- package/array.js +162 -0
- package/cache.js +7 -0
- package/cdn.js +83 -0
- package/component.js +25 -0
- package/cookie.js +71 -0
- package/{src/detectHeight.js → detectHeight.js} +3 -3
- package/dist/cjs/array.js +168 -0
- package/dist/cjs/browser.js +0 -2
- package/dist/cjs/cache.js +26 -0
- package/dist/cjs/cdn.js +85 -0
- package/dist/cjs/component.js +45 -0
- package/dist/cjs/cookie.js +75 -0
- package/dist/cjs/date.js +1 -3
- package/dist/cjs/detectHeight.js +4 -6
- package/dist/cjs/element.js +136 -0
- package/dist/cjs/env.js +43 -0
- package/dist/cjs/events.js +39 -0
- package/dist/cjs/extends.js +434 -0
- package/dist/cjs/fibonacci.js +1 -3
- package/dist/cjs/files.js +1 -3
- package/dist/cjs/function.js +114 -0
- package/dist/cjs/globals.js +30 -0
- package/dist/cjs/if.js +48 -0
- package/dist/cjs/index.js +38 -425
- package/dist/cjs/key.js +38 -0
- package/dist/cjs/keys.js +201 -0
- package/dist/cjs/load.js +5 -7
- package/dist/cjs/log.js +34 -0
- package/dist/cjs/metadata.js +310 -0
- package/dist/cjs/methods.js +599 -0
- package/dist/cjs/node.js +35 -0
- package/dist/cjs/object.js +634 -0
- package/dist/cjs/projectKeys.js +35 -0
- package/dist/cjs/props.js +282 -0
- package/dist/cjs/scaling.js +5 -169
- package/dist/cjs/scope.js +44 -0
- package/dist/cjs/sharedLibraries.js +146 -0
- package/dist/cjs/state.js +173 -0
- package/dist/cjs/string-extra.js +72 -0
- package/dist/cjs/string.js +183 -0
- package/dist/cjs/style.js +1 -3
- package/dist/cjs/tags.js +220 -0
- package/dist/cjs/triggerEvent.js +112 -0
- package/dist/cjs/types.js +74 -0
- package/dist/cjs/update.js +42 -0
- package/dist/esm/array.js +148 -0
- package/dist/esm/browser.js +14 -0
- package/dist/esm/cache.js +6 -0
- package/dist/esm/cdn.js +65 -0
- package/dist/esm/component.js +25 -0
- package/dist/esm/cookie.js +55 -0
- package/dist/esm/date.js +11 -0
- package/dist/esm/detectHeight.js +42 -0
- package/dist/esm/element.js +116 -0
- package/dist/esm/env.js +23 -0
- package/dist/esm/events.js +19 -0
- package/dist/esm/extends.js +414 -0
- package/dist/esm/fibonacci.js +10 -0
- package/dist/esm/files.js +4 -0
- package/dist/esm/function.js +94 -0
- package/dist/esm/globals.js +10 -0
- package/dist/esm/if.js +28 -0
- package/dist/esm/index.js +37 -0
- package/dist/esm/key.js +18 -0
- package/dist/esm/keys.js +181 -0
- package/dist/esm/load.js +151 -0
- package/dist/esm/log.js +14 -0
- package/dist/esm/metadata.js +290 -0
- package/dist/esm/methods.js +569 -0
- package/dist/esm/node.js +15 -0
- package/dist/esm/object.js +621 -0
- package/dist/esm/projectKeys.js +16 -0
- package/dist/esm/props.js +262 -0
- package/dist/esm/scaling.js +15 -0
- package/dist/esm/scope.js +24 -0
- package/dist/esm/sharedLibraries.js +126 -0
- package/dist/esm/state.js +163 -0
- package/dist/esm/string-extra.js +52 -0
- package/dist/esm/string.js +163 -0
- package/dist/esm/style.js +20 -0
- package/dist/esm/tags.js +200 -0
- package/dist/esm/triggerEvent.js +92 -0
- package/dist/esm/types.js +54 -0
- package/dist/esm/update.js +22 -0
- package/dist/iife/index.js +5818 -0
- package/element.js +148 -0
- package/env.js +15 -0
- package/events.js +19 -0
- package/extends.js +519 -0
- package/function.js +145 -0
- package/globals.js +8 -0
- package/if.js +30 -0
- package/index.js +41 -0
- package/key.js +20 -0
- package/keys.js +176 -0
- package/log.js +12 -0
- package/metadata.js +392 -0
- package/methods.js +636 -0
- package/node.js +37 -0
- package/object.js +803 -0
- package/package.json +38 -22
- package/projectKeys.js +30 -0
- package/props.js +327 -0
- package/{src/scaling.js → scaling.js} +1 -1
- package/scope.js +32 -0
- package/sharedLibraries.js +184 -0
- package/state.js +203 -0
- package/{src/index.js → string-extra.js} +11 -17
- package/string.js +206 -0
- package/tags.js +176 -0
- package/triggerEvent.js +102 -0
- package/types.js +55 -0
- package/update.js +27 -0
- package/dist/cjs/package.json +0 -4
- /package/{src/browser.js → browser.js} +0 -0
- /package/{src/date.js → date.js} +0 -0
- /package/{src/fibonacci.js → fibonacci.js} +0 -0
- /package/{src/files.js → files.js} +0 -0
- /package/{src/load.js → load.js} +0 -0
- /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.
|
|
4
|
-
"
|
|
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
|
-
"
|
|
7
|
-
"
|
|
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": "
|
|
20
|
-
"build:cjs": "
|
|
21
|
-
"build
|
|
22
|
-
"
|
|
23
|
-
"
|
|
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
|
-
"
|
|
26
|
-
|
|
27
|
-
"@domql/event": "^3.2.3",
|
|
28
|
-
"@domql/utils": "^3.2.3"
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@babel/core": "^7.26.0"
|
|
29
39
|
},
|
|
30
|
-
"
|
|
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
|
+
}
|
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
|
+
}
|