smbls 3.14.2 → 3.14.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/src/prepare.js DELETED
@@ -1,526 +0,0 @@
1
- 'use strict'
2
-
3
- import {
4
- isObject,
5
- deepMerge,
6
- deepClone,
7
- merge,
8
- isDevelopment,
9
- matchesComponentNaming,
10
- mergeSharedLibraries,
11
- PACKAGE_MANAGER_TO_CDN,
12
- getCdnProviderFromConfig,
13
- getCDNUrl
14
- } from '@symbo.ls/utils'
15
- import { css, injectGlobal } from '@symbo.ls/css'
16
- import { DEFAULT_CONFIG } from '@symbo.ls/default-config'
17
- import { init } from './init.js'
18
- import { getActiveConfig, createConfig, pushConfig, popConfig } from '@symbo.ls/scratch'
19
- import { DEFAULT_CONTEXT } from './options.js'
20
-
21
- // FT-FRAMEWORK-1: source from the explicit COMPONENTS manifest, not from
22
- // `import * as uikit`. Parcel under `sideEffects: false` was pruning entries
23
- // off the namespace import — projects ended up with atoms missing from
24
- // `context.components`. The manifest is a plain object literal so the
25
- // bundler can't mistake any entry for an unused import.
26
- import { COMPONENTS as uikit } from '@symbo.ls/default-config/components'
27
- import * as utils from './utilImports.js'
28
- import * as routerUtils from '@symbo.ls/router'
29
-
30
- // Re-export so existing consumers still find them here
31
- export { PACKAGE_MANAGER_TO_CDN, getCdnProviderFromConfig, getCDNUrl }
32
-
33
- // @preserve-env
34
-
35
- export const prepareWindow = (context) => {
36
- const win = typeof window !== 'undefined' ? window : globalThis || {}
37
- if (!win.document) win.document = { body: {} }
38
- const doc = typeof document !== 'undefined' ? document : win.document
39
- context.document = context.document || doc
40
- context.window = context.window || win
41
- return context.window
42
- }
43
-
44
- const UIkitWithPrefix = (prefix = 'smbls') => {
45
- const newObj = {}
46
- for (const key in uikit) {
47
- if (Object.prototype.hasOwnProperty.call(uikit, key)) {
48
- if (matchesComponentNaming(key)) {
49
- newObj[`smbls.${key}`] = uikit[key]
50
- } else {
51
- newObj[key] = uikit[key]
52
- }
53
- }
54
- }
55
- return newObj
56
- }
57
-
58
- export const prepareComponents = (context) => {
59
- return context.components
60
- ? { ...UIkitWithPrefix(), ...context.components }
61
- : UIkitWithPrefix()
62
- }
63
-
64
- export const prepareUtils = (context) => {
65
- return {
66
- ...utils,
67
- ...routerUtils,
68
- ...utils.scratchUtils,
69
- ...context.utils,
70
- ...context.snippets,
71
- ...context.functions
72
- }
73
- }
74
-
75
- export const prepareMethods = (context) => {
76
- return {
77
- ...(context.methods || {}),
78
- require: context.utils.require,
79
- requireOnDemand: context.utils.requireOnDemand,
80
- router: context.utils.router
81
- }
82
- }
83
-
84
- const cachedDeps = {}
85
- export const prepareDependencies = async ({
86
- dependencies,
87
- dependenciesOnDemand,
88
- document,
89
- preventCaching = false,
90
- cdnProvider,
91
- packageManager,
92
- symbolsConfig
93
- }) => {
94
- // Derive provider from packageManager or symbolsConfig when not explicitly passed
95
- if (!cdnProvider) {
96
- cdnProvider =
97
- PACKAGE_MANAGER_TO_CDN[packageManager] ||
98
- getCdnProviderFromConfig(symbolsConfig) ||
99
- 'esmsh'
100
- }
101
- if (!dependencies) return null
102
- let hasAny = false
103
- for (const _k in dependencies) {
104
- hasAny = true
105
- break
106
- } // eslint-disable-line
107
- if (!hasAny) return null
108
-
109
- for (const dependency in dependencies) {
110
- const version = dependencies[dependency]
111
- if (dependenciesOnDemand && dependenciesOnDemand[dependency]) {
112
- continue
113
- }
114
-
115
- const random = isDevelopment() && preventCaching ? `?${Math.random()}` : ''
116
- const url = getCDNUrl(dependency, version, cdnProvider) + random
117
-
118
- try {
119
- if (cachedDeps[dependency]) continue
120
- cachedDeps[dependency] = true
121
- await Promise.race([
122
- utils.loadRemoteScript(url, { document, type: 'module' }),
123
- new Promise((_, r) => setTimeout(() => r(new Error(`Timeout loading ${dependency}`)), 10000))
124
- ])
125
- } catch (e) {
126
- console.error(`Failed to load ${dependency} from ${cdnProvider}:`, e)
127
-
128
- if (cdnProvider !== 'symbols') {
129
- try {
130
- const fallbackUrl = getCDNUrl(dependency, version, 'symbols') + random
131
- await Promise.race([
132
- utils.loadRemoteScript(fallbackUrl, { document }),
133
- new Promise((_, r) => setTimeout(() => r(new Error(`Timeout fallback ${dependency}`)), 10000))
134
- ])
135
- console.log(
136
- `Successfully loaded ${dependency} from fallback (symbols.ls)`
137
- )
138
- } catch (fallbackError) {
139
- console.error(
140
- `Failed to load ${dependency} from fallback:`,
141
- fallbackError
142
- )
143
- }
144
- }
145
- }
146
- }
147
-
148
- return dependencies
149
- }
150
-
151
- export const prepareRequire = async (packages, ctx) => {
152
- const windowOpts = ctx.window || window
153
- const defaultProvider =
154
- ctx.cdnProvider || getCdnProviderFromConfig(ctx.symbolsConfig) || 'esmsh'
155
-
156
- const initRequire = async (ctx) => async (key, provider) => {
157
- const windowOpts = ctx.window || window
158
- const pkg = windowOpts.packages[key]
159
- if (typeof pkg === 'function') return pkg()
160
- return pkg
161
- }
162
-
163
- const initRequireOnDemand =
164
- async (ctx) =>
165
- async (key, provider = defaultProvider) => {
166
- const { dependenciesOnDemand } = ctx
167
- const documentOpts = ctx.document || document
168
- const windowOpts = ctx.window || window
169
- if (!windowOpts.packages[key]) {
170
- const random = isDevelopment() ? `?${Math.random()}` : ''
171
- if (dependenciesOnDemand && dependenciesOnDemand[key]) {
172
- const version = dependenciesOnDemand[key]
173
- const url = getCDNUrl(key, version, provider) + random
174
- try {
175
- await ctx.utils.loadRemoteScript(url, {
176
- window: windowOpts,
177
- document: documentOpts
178
- })
179
- } catch (e) {
180
- console.error(`Failed to load ${key} from ${provider}:`, e)
181
- // Fallback to symbo if not already using it
182
- if (provider !== 'symbols') {
183
- const fallbackUrl = getCDNUrl(key, version, 'symbols') + random
184
- await ctx.utils.loadRemoteScript(fallbackUrl, {
185
- window: windowOpts,
186
- document: documentOpts
187
- })
188
- }
189
- }
190
- } else {
191
- const url = getCDNUrl(key, 'latest', provider) + random
192
- try {
193
- await ctx.utils.loadRemoteScript(url, {
194
- window: windowOpts,
195
- document: documentOpts
196
- })
197
- } catch (e) {
198
- console.error(`Failed to load ${key} from ${provider}:`, e)
199
- // Fallback to symbo if not already using it
200
- if (provider !== 'symbols') {
201
- const fallbackUrl = getCDNUrl(key, 'latest', 'symbols') + random
202
- await ctx.utils.loadRemoteScript(fallbackUrl, {
203
- window: windowOpts,
204
- document: documentOpts
205
- })
206
- }
207
- }
208
- windowOpts.packages[key] = 'loadedOnDeman'
209
- }
210
- }
211
- return await windowOpts.require(key, provider)
212
- }
213
-
214
- if (windowOpts.packages) {
215
- windowOpts.packages = merge(windowOpts.packages, packages)
216
- } else {
217
- windowOpts.packages = packages
218
- }
219
-
220
- if (!windowOpts.require) {
221
- ctx.utils.require = await initRequire(ctx)
222
- windowOpts.require = ctx.utils.require
223
- }
224
-
225
- if (!windowOpts.requireOnDemand) {
226
- ctx.utils.requireOnDemand = await initRequireOnDemand(ctx)
227
- windowOpts.requireOnDemand = ctx.utils.requireOnDemand
228
- }
229
- }
230
-
231
- // --- CSS classlist/style transform handlers ---
232
-
233
- const RE_MULTI_SPACE = /\s+/g
234
-
235
- const execParam = (param, element) => {
236
- if (typeof param === 'function') return param(element, element?.state, element?.context)
237
- return param
238
- }
239
-
240
- const classify = (obj, element) => {
241
- let className = ''
242
- for (const item in obj) {
243
- const param = obj[item]
244
- if (typeof param === 'boolean' && param) className += ` ${item}`
245
- else if (typeof param === 'string') className += ` ${param}`
246
- else if (typeof param === 'function') {
247
- className += ` ${execParam(param, element)}`
248
- }
249
- }
250
- return className
251
- }
252
-
253
- const applyClassListOnNode = (params, element, node) => {
254
- if (!params) return
255
- const { key } = element
256
- if (params === true) params = element.classlist = { key }
257
- if (typeof params === 'string') params = element.classlist = { default: params }
258
- if (params !== null && typeof params === 'object' && !Array.isArray(params)) {
259
- params = classify(params, element)
260
- }
261
- const className = params.replace(RE_MULTI_SPACE, ' ').trim()
262
- if (node && typeof node.setAttribute === 'function') {
263
- node.setAttribute('class', className)
264
- } else if (node) {
265
- try { node.className = className } catch (e) { /* SVG element */ }
266
- }
267
- return className
268
- }
269
-
270
- const transformCssStyle = () => {
271
- return (params, element, state) => {
272
- const execParams = execParam(params, element)
273
- if (params) {
274
- const { __ref: ref } = element
275
- ref.__class.style = execParams
276
- }
277
- transformCssClass()(
278
- element.classlist,
279
- element,
280
- element.state,
281
- true
282
- )
283
- }
284
- }
285
-
286
- const transformCssClass = () => {
287
- return (params, element, state, flag) => {
288
- if (element.style && !flag) return
289
- const { __ref } = element
290
- const { __class, __classNames } = __ref
291
-
292
- if (params === null || typeof params !== 'object' || Array.isArray(params)) return
293
- if (element.class) {
294
- __classNames.classProps = element.class
295
- }
296
- if (element.attr?.class) {
297
- __classNames.class = element.attr.class
298
- }
299
-
300
- for (const key in __class) {
301
- const prop = __class[key]
302
- if (!prop) {
303
- delete __classNames[key]
304
- continue
305
- }
306
- let className
307
- if (typeof prop === 'string' || typeof prop === 'number') className = prop
308
- else if (typeof prop === 'boolean') className = element.key
309
- else className = css(prop)
310
- __classNames[key] = className
311
- }
312
-
313
- // Remove stale classNames that no longer exist in __class
314
- for (const key in __classNames) {
315
- if (key === 'classProps' || key === 'class') continue
316
- if (__class[key] === undefined) {
317
- delete __classNames[key]
318
- }
319
- }
320
-
321
- if (element.node) applyClassListOnNode(__classNames, element, element.node)
322
- }
323
- }
324
-
325
- const createCssRegistry = () => {
326
- return {
327
- style: transformCssStyle(),
328
- classlist: transformCssClass()
329
- }
330
- }
331
-
332
- // --- Design system preparation ---
333
-
334
- const OPTION_KEYS = [
335
- 'useReset', 'useVariable', 'useFontImport', 'useIconSprite',
336
- 'useSvgSprite', 'useDocumentTheme', 'useDefaultIcons', 'useDefaultConfig', 'verbose',
337
- 'globalTheme'
338
- ]
339
-
340
- let _designSystemInitialized = false
341
-
342
- export const prepareDesignSystem = (key, context) => {
343
- const doc = context.document || (context.parent && context.parent.documentElement ? context.parent : null) || document
344
- const initOptions = context.initOptions || {}
345
-
346
- const registry = context.registry || createCssRegistry()
347
- const designSystem = context.designSystem
348
- ? deepMerge(deepClone(context.designSystem), DEFAULT_CONFIG)
349
- : deepClone(DEFAULT_CONFIG)
350
-
351
- // Pick context-level overrides (from user's config.js spread into context)
352
- const contextOverrides = {}
353
- for (const k of OPTION_KEYS) {
354
- if (context[k] !== undefined) contextOverrides[k] = context[k]
355
- }
356
-
357
- let scratchSystem
358
- const isSecondary = _designSystemInitialized && !context.skipDesignSystemInit
359
- const scopeSelector = isSecondary ? `[data-smbls-app="${key}"]` : ':root'
360
- // Expose for downstream consumers (element create.js stamps the attr)
361
- context.scopeSelector = scopeSelector
362
- const themeScope = scopeSelector === ':root'
363
- ? ':root:not([data-theme])'
364
- : `${scopeSelector}:not([data-theme])`
365
- if (context.skipDesignSystemInit) {
366
- // Reuse existing config (e.g. iframe context where init() already ran on parent)
367
- scratchSystem = getActiveConfig(key) || getActiveConfig()
368
- // Store the target document on the config so getActiveDocument()
369
- // resolves it from the config stack.
370
- if (scratchSystem) scratchSystem.document = doc
371
- // Re-inject CSS vars into the target document via active document
372
- if (scratchSystem?.CSS_VARS) {
373
- pushConfig(scratchSystem)
374
- try {
375
- injectGlobal({ [scopeSelector]: scratchSystem.CSS_VARS })
376
- if (scratchSystem.CSS_MEDIA_VARS) {
377
- const themeStyles = {}
378
- for (const key in scratchSystem.CSS_MEDIA_VARS) {
379
- if (key.startsWith('@media')) {
380
- themeStyles[key] = { [themeScope]: scratchSystem.CSS_MEDIA_VARS[key] }
381
- } else {
382
- themeStyles[key] = scratchSystem.CSS_MEDIA_VARS[key]
383
- }
384
- }
385
- injectGlobal(themeStyles)
386
- }
387
- } finally {
388
- popConfig()
389
- }
390
- }
391
- } else if (isSecondary) {
392
- // Secondary app (same-document embedding OR iframe-embedded) — scope
393
- // cssVars to `[data-smbls-app="<key>"]` so the project DS does NOT leak
394
- // into the primary app's subtree, AND pass `cleanBase: true` so the
395
- // isolated config does not inherit the primary's processed tokens
396
- // (e.g. editor brand themes leaking into project iframe).
397
- const isolatedConfig = createConfig(key, designSystem, { cleanBase: true })
398
- for (const k in contextOverrides) {
399
- isolatedConfig[k] = contextOverrides[k]
400
- }
401
- // Store the target document on the config so getActiveDocument()
402
- // resolves it from the config stack (no global singleton needed).
403
- isolatedConfig.document = doc
404
- // Derive CSS prefix for multi-app class name isolation
405
- const cssPrefix = key.replace(/[^a-zA-Z0-9]/g, '').substring(0, 6)
406
- context.cssPrefix = cssPrefix
407
- isolatedConfig.varPrefix = cssPrefix
408
- pushConfig(isolatedConfig)
409
- try {
410
- scratchSystem = init(isolatedConfig, {
411
- key,
412
- document: doc,
413
- files: context.files,
414
- assets: context.assets,
415
- scopeSelector,
416
- ...DEFAULT_CONTEXT,
417
- ...contextOverrides,
418
- ...initOptions
419
- })
420
- } finally {
421
- popConfig()
422
- }
423
- } else {
424
- // Primary app — use global config path (`:root`)
425
- // Store the target document on the global config before init() so
426
- // getActiveDocument() resolves it during injectGlobal() calls.
427
- const globalConfig = getActiveConfig()
428
- if (globalConfig) globalConfig.document = doc
429
- scratchSystem = init(designSystem, {
430
- key,
431
- document: doc,
432
- files: context.files,
433
- assets: context.assets,
434
- scopeSelector,
435
- ...DEFAULT_CONTEXT,
436
- ...contextOverrides,
437
- ...initOptions
438
- })
439
- // Ensure the returned config also carries the document reference
440
- if (scratchSystem && scratchSystem !== globalConfig) scratchSystem.document = doc
441
- _designSystemInitialized = true
442
- }
443
-
444
- // CSS reset is injected in createDomql.js after sheet is guaranteed to exist
445
-
446
- // Scoped inside pushConfig so getActiveDocument() resolves to this app's
447
- // doc (the iframe's for secondary apps); otherwise @font-face rules land
448
- // in the parent sheet and the iframe never sees them.
449
- const activeConfig = scratchSystem?.CONFIG || scratchSystem
450
- const fontConfig = activeConfig?.font
451
- if (fontConfig) {
452
- pushConfig(activeConfig)
453
- try {
454
- for (const fontKey in fontConfig) {
455
- const fontData = fontConfig[fontKey]
456
- if (!fontData?.fontFace) continue
457
- const faces = Array.isArray(fontData.fontFace) ? fontData.fontFace : [fontData.fontFace]
458
- for (const face of faces) {
459
- if (typeof face === 'string' && face.includes('font-family')) {
460
- injectGlobal(`@font-face { ${face} }`)
461
- }
462
- }
463
- }
464
- } finally {
465
- popConfig()
466
- }
467
- }
468
-
469
- return [scratchSystem, registry]
470
- }
471
-
472
- export const prepareState = (app, context) => {
473
- const state = {}
474
- if (context.state) utils.deepMerge(state, context.state)
475
- if (app && app.state) deepMerge(state, app.state)
476
-
477
- // Merge polyglot translations for active language into state
478
- const poly = context.polyglot
479
- if (poly?.translations) {
480
- // Check localStorage for persisted language preference. Wrap the
481
- // access — sandboxed iframes / Firefox storage-disabled mode throw on
482
- // .getItem(), not just on .setItem().
483
- let storedLang = null
484
- try {
485
- if (typeof localStorage !== 'undefined') {
486
- storedLang = localStorage.getItem(poly.storageLangKey || 'smbls_lang') || localStorage.getItem('lang')
487
- }
488
- } catch (e) {}
489
- const lang = storedLang || state.lang || poly.defaultLang || 'en'
490
- if (storedLang && storedLang !== state.lang) state.lang = storedLang
491
- const langT = poly.translations[lang]
492
- if (langT && typeof langT === 'object') {
493
- for (const k in langT) {
494
- state[k] = langT[k] // Override with active language translations
495
- }
496
- }
497
- // Also merge top-level translation keys (skip language code keys)
498
- const langs = new Set(poly.languages || [])
499
- for (const k in poly.translations) {
500
- if (!langs.has(k) && state[k] === undefined) {
501
- state[k] = poly.translations[k]
502
- }
503
- }
504
- }
505
-
506
- state.isRootState = true
507
- return deepClone(state)
508
- }
509
-
510
- export const preparePages = (app, context) => {
511
- if (isObject(app.routes) && isObject(context.pages)) {
512
- merge(app.routes, context.pages)
513
- }
514
- const pages = app.routes || context.pages || {}
515
- for (const v in pages) {
516
- if (v.charCodeAt(0) === 47) continue // '/'
517
- const index = v === 'index' ? '' : v
518
- pages['/' + index] = pages[v]
519
- delete pages[v]
520
- }
521
- return pages
522
- }
523
-
524
- export const prepareSharedLibs = (context) => {
525
- mergeSharedLibraries(context, context.sharedLibraries)
526
- }
package/src/router.js DELETED
@@ -1,107 +0,0 @@
1
- 'use strict'
2
-
3
- import { window, deepMerge, merge, isUndefined } from '@symbo.ls/utils'
4
- import { router as defaultRouter } from '@symbo.ls/router'
5
-
6
- const normalizePath = (p) => (!p || p === 'srcdoc' || p === 'about:srcdoc' || p === 'blank' || p === 'about:blank') ? '/' : p
7
-
8
- const DEFAULT_ROUTING_OPTIONS = {
9
- initRouter: true,
10
- injectRouterInLinkComponent: true,
11
- popState: true
12
- }
13
-
14
- export const resolveRouterElement = (root, path) => {
15
- if (!path) return root
16
- const parts = Array.isArray(path) ? path : path.split('.')
17
- let el = root
18
- for (const part of parts) {
19
- if (!el || !el[part]) return null
20
- el = el[part]
21
- }
22
- return el
23
- }
24
-
25
- export const initRouter = (element, context) => {
26
- if (context.router === false) return
27
- else if (context.router === true) context.router = DEFAULT_ROUTING_OPTIONS
28
- else context.router = merge(context.router || {}, DEFAULT_ROUTING_OPTIONS)
29
-
30
- const routerOptions = context.router
31
-
32
- const onRouterRenderDefault = async (el, s) => {
33
- const win = el.context?.window || window
34
- if (!win.location) return
35
- let { pathname, search, hash } = win.location
36
- pathname = normalizePath(pathname)
37
- const url = pathname + search + hash
38
-
39
- let targetEl = el
40
- if (routerOptions.customRouterElement) {
41
- const resolved = resolveRouterElement(el, routerOptions.customRouterElement)
42
- if (resolved) {
43
- targetEl = resolved
44
- if (el.routes) targetEl.routes = el.routes
45
- }
46
- }
47
-
48
- if (targetEl.routes) await defaultRouter(url, targetEl, {}, { initialRender: true, pushState: false })
49
- }
50
-
51
- const hasRenderRouter = !isUndefined(element.onRenderRouter)
52
- if (routerOptions && routerOptions.initRouter && !hasRenderRouter) {
53
- element.onRenderRouter = onRouterRenderDefault
54
- }
55
-
56
- injectRouterInLinkComponent(context, routerOptions)
57
-
58
- return routerOptions
59
- }
60
-
61
- export const onpopstateRouter = (element, context) => {
62
- if (context.router === false) return
63
- const routerOptions = context.router || DEFAULT_ROUTING_OPTIONS
64
- if (!routerOptions.popState) return
65
- const router =
66
- context.utils && context.utils.router ? context.utils.router : defaultRouter
67
-
68
- const win = context.window || window
69
- // addEventListener (not onpopstate=) so multiple apps in the same realm
70
- // don't clobber each other's handlers. The teardown is queued on
71
- // context.__teardowns so destroy(app) can remove it cleanly.
72
- const handler = async e => {
73
- let { pathname, search, hash } = win.location
74
- pathname = normalizePath(pathname)
75
- const url = pathname + search + hash
76
-
77
- let targetEl = element
78
- if (routerOptions.customRouterElement) {
79
- const resolved = resolveRouterElement(element, routerOptions.customRouterElement)
80
- if (resolved) {
81
- targetEl = resolved
82
- if (element.routes) targetEl.routes = element.routes
83
- }
84
- }
85
-
86
- await targetEl.call(
87
- 'router',
88
- url,
89
- targetEl,
90
- {},
91
- { pushState: false, scrollToTop: false, level: 0, event: e }
92
- )
93
- }
94
- win.addEventListener('popstate', handler)
95
- context.__teardowns = context.__teardowns || []
96
- context.__teardowns.push(() => win.removeEventListener('popstate', handler))
97
- }
98
-
99
- export const injectRouterInLinkComponent = (context, routerOptions) => {
100
- const { components } = context
101
- if (routerOptions && routerOptions.injectRouterInLinkComponent) {
102
- return deepMerge(
103
- components['Link'] || components['smbls.Link'],
104
- components['RouterLink'] || components['smbls.RouterLink']
105
- )
106
- }
107
- }
package/src/syncExtend.js DELETED
@@ -1,17 +0,0 @@
1
- 'use strict'
2
-
3
- import { isObjectLike } from '@symbo.ls/utils'
4
-
5
- // All sync-related auto-injection moved to plugins:
6
- // - `syncPlugin` (HMR) in `@symbo.ls/sync`
7
- // - `notificationsPlugin` (toasts) in `@symbo.ls/sync`
8
- // - `inspectPlugin` (overlay) in `@symbo.ls/inspect`
9
- // The framework registers them through the generic `context.plugins`
10
- // mechanism in `prepareContext` (`./createDomql.js`). This file only
11
- // exposes the lone helper that survived: `initializeExtend`, which
12
- // normalises `app.extends` to an array before any plugin's
13
- // `beforeCreate` runs.
14
-
15
- export const initializeExtend = (app, ctx) => {
16
- return isObjectLike(app.extends) ? app.extends : []
17
- }
@@ -1,8 +0,0 @@
1
- 'use strict'
2
-
3
- import { scratchUtils, scratchSystem, set } from '@symbo.ls/scratch'
4
-
5
- export { scratchUtils, scratchSystem, set }
6
- export * from '@symbo.ls/utils'
7
- export { init, reinit, applyCSS } from './init.js'
8
- export * from '@symbo.ls/router'