smbls 3.14.0 → 3.14.2

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.
@@ -0,0 +1,4 @@
1
+ {
2
+ "type": "commonjs",
3
+ "main": "index.js"
4
+ }
package/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict'
2
2
 
3
- // v4 entry point - re-exports all framework packages
3
+ // v3.14 entry point - re-exports all framework packages
4
4
 
5
- // Core v4 primitives (NEW)
5
+ // Core v3.14 primitives (NEW)
6
6
  export * from '@symbo.ls/signal'
7
7
  export * from '@symbo.ls/css'
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smbls",
3
- "version": "3.14.0",
3
+ "version": "3.14.2",
4
4
  "license": "CC-BY-NC-4.0",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -20,6 +20,7 @@
20
20
  "src"
21
21
  ],
22
22
  "dependencies": {
23
+ "@symbo.ls/analyze": "^3.14.0",
23
24
  "@symbo.ls/signal": "^3.14.0",
24
25
  "@symbo.ls/element": "^3.14.0",
25
26
  "@symbo.ls/state": "^3.14.0",
@@ -4,14 +4,10 @@ import { create as createElement } from '@symbo.ls/element'
4
4
  import * as uikit from '@symbo.ls/default-config/components'
5
5
  import { CSS_PROPS_REGISTRY } from 'css-in-props'
6
6
 
7
- import { isString, isNode, isObject, destringifyGlobalScope, deepDestringifyFunctions } from '@symbo.ls/utils'
7
+ import { isString, isNode, isObject, destringifyGlobalScope, deepDestringifyFunctions, setActiveRootState } from '@symbo.ls/utils'
8
8
  import { defaultDefine } from './define.js'
9
9
  import { initRouter } from './router.js'
10
- import {
11
- initializeExtend,
12
- initializeNotifications,
13
- initializeSync
14
- } from './syncExtend.js'
10
+ import { initializeExtend } from './syncExtend.js'
15
11
 
16
12
  import {
17
13
  prepareComponents,
@@ -34,6 +30,8 @@ import { fetchPlugin } from '@symbo.ls/fetch'
34
30
  import { capsizePlugin, capsizeHandler } from '@symbo.ls/capsize'
35
31
  import { shorthandPlugin } from '@symbo.ls/shorthand'
36
32
  import { analyzePlugin, createAnalyzeState } from '@symbo.ls/analyze'
33
+ import { syncPlugin, notificationsPlugin } from '@symbo.ls/sync'
34
+ import { inspectPlugin } from '@symbo.ls/inspect'
37
35
 
38
36
  // hydrate utilities available for future true-hydration opt-in
39
37
  // import { hydrate, assignBrKeysFromRegistry } from './hydrate.js'
@@ -77,6 +75,12 @@ export const prepareContext = async (app, context = {}) => {
77
75
 
78
76
  const state = prepareState(app, context)
79
77
  context.state = state
78
+ // Register the active root state in the framework registry so
79
+ // `getRootState()` (called without `this`) can find it during early
80
+ // render. Eliminates the requirement that projects pre-set
81
+ // `window.smblsApp = { state }` in their `index.js` — the runner
82
+ // bypasses project-side index.js entirely, and now no longer needs to.
83
+ setActiveRootState(state)
80
84
  context.pages = preparePages(app, context)
81
85
  context.components = prepareComponents(context)
82
86
  context.utils = prepareUtils(context)
@@ -104,7 +108,7 @@ export const prepareContext = async (app, context = {}) => {
104
108
  // nominal type — invalid CSS for `transform`, dead listeners for
105
109
  // `onClick`, raw source code shown for `text`, etc. — which is the root
106
110
  // cause of every "works on dev / dead on remote" divergence we've ever
107
- // seen for v4 published projects. Walks the same buckets prepareContext
111
+ // seen for v3.14 published projects. Walks the same buckets prepareContext
108
112
  // populated above, mutating each into a tree where every detectable
109
113
  // function-string has been `eval`'d back to a real function.
110
114
  // ESM-namespace-shaped buckets (`import * as functions from ...`) have
@@ -170,6 +174,36 @@ export const prepareContext = async (app, context = {}) => {
170
174
  context.plugins.push(analyzePlugin)
171
175
  }
172
176
 
177
+ // Sync plugin — HMR module for both transports (mermaid-served collab and
178
+ // runner-served local). Plugged ON by default; users opt out by setting
179
+ // `context.sync = false` (or `editor.liveSync = false`) or by removing
180
+ // syncPlugin from `context.plugins` before calling `create()`. With
181
+ // `context.sync = false` the plugin is never pushed, so SyncComponent
182
+ // never reaches `app.extends` and no socket connection is attempted.
183
+ if (context.sync !== false && !hasPlugin('sync')) {
184
+ context.plugins.push(syncPlugin)
185
+ }
186
+
187
+ // Inspect plugin — element inspector overlay. Default OFF. Opt-in by
188
+ // setting `context.inspect = true` (or a config object) — this auto-
189
+ // registers the plugin which adds Alt+Shift highlight + click capture.
190
+ // See `@symbo.ls/inspect` README for the shortcut + `editor.onInspect`
191
+ // callback contract.
192
+ if (context.inspect && !hasPlugin('inspect')) {
193
+ context.plugins.push(inspectPlugin)
194
+ }
195
+
196
+ // Notifications plugin — toast overlay for sync connect/disconnect.
197
+ // Default OFF. Opt-in via `context.notifications = true` (or the
198
+ // legacy `editor.verbose = true` for backwards compat). Plugin's
199
+ // beforeCreate filters again so flipping `context.notifications =
200
+ // false` short-circuits even if the plugin is pushed manually.
201
+ const notificationsOptIn = context.notifications === true ||
202
+ (context.editor && context.editor.verbose === true)
203
+ if (notificationsOptIn && context.notifications !== false && !hasPlugin('notifications')) {
204
+ context.plugins.push(notificationsPlugin)
205
+ }
206
+
173
207
  return context
174
208
  }
175
209
 
@@ -242,8 +276,20 @@ export const createDomqlElement = async (app, ctx) => {
242
276
  )
243
277
  }
244
278
 
245
- initializeSync(app, ctx)
246
- initializeNotifications(app, ctx)
279
+ // Run plugin `beforeCreate` lifecycle. All sync/notifications/inspect
280
+ // injection happens here — the framework knows nothing about which
281
+ // plugins run. Remove the relevant plugin from `ctx.plugins` (or set
282
+ // `context.sync = false` / `context.inspect = false` / etc) and no
283
+ // attach happens.
284
+ for (const plugin of ctx.plugins) {
285
+ if (typeof plugin.beforeCreate === 'function') {
286
+ try {
287
+ plugin.beforeCreate(app, ctx)
288
+ } catch (err) {
289
+ console.error(`[smbls] plugin "${plugin.name || '?'}" beforeCreate error:`, err)
290
+ }
291
+ }
292
+ }
247
293
 
248
294
  const doc = ctx.document
249
295
  if (!doc || !doc.createElement) return app
@@ -254,7 +300,7 @@ export const createDomqlElement = async (app, ctx) => {
254
300
  return fallbackRender(createElement, app, parentNode, ctx, win, ssrStyles)
255
301
  }
256
302
 
257
- // v4: use @symbo.ls/element create directly
303
+ // v3.14: use @symbo.ls/element create directly
258
304
  const smblsApp = createElement(app, parentNode, ctx.key, {
259
305
  verbose: ctx.verbose,
260
306
  context: ctx,
package/src/hydrate.js CHANGED
@@ -436,7 +436,7 @@ const bindEvents = (el) => {
436
436
 
437
437
  if (!el.__ref.__eventCleanup) el.__ref.__eventCleanup = []
438
438
 
439
- // v4: event handlers are flat on element as onX properties
439
+ // v3.14: event handlers are flat on element as onX properties
440
440
  for (const key in el) {
441
441
  if (key.length <= 2 || key[0] !== 'o' || key[1] !== 'n') continue
442
442
  if (typeof el[key] !== 'function') continue
package/src/index.js CHANGED
@@ -81,6 +81,36 @@ const resolveAndApplyTheme = (options) => {
81
81
  options.themeRoot = scopeRoot
82
82
  }
83
83
 
84
+ // Read `<meta name="symbols-runtime" content="...">` from the served
85
+ // document. This is how the runner signals its mode to the framework
86
+ // without a window global — it injects the meta tag into the served
87
+ // HTML, the framework reads it once at create() time and stamps the
88
+ // matching `editor.runtime` so syncPlugin (and anyone else who cares)
89
+ // can detect runner mode via context.
90
+ const readRuntimeMeta = () => {
91
+ if (typeof document === 'undefined') return null
92
+ try {
93
+ const m = document.querySelector('meta[name="symbols-runtime"]')
94
+ const v = m && m.getAttribute('content')
95
+ return (typeof v === 'string' && v) ? v : null
96
+ } catch (_) {
97
+ return null
98
+ }
99
+ }
100
+
101
+ const stampRuntimeFromMeta = (options) => {
102
+ const runtime = readRuntimeMeta()
103
+ if (!runtime) return
104
+ if (!options.editor) options.editor = {}
105
+ // Don't clobber an explicit project setting — projects can override
106
+ // by setting `editor.runtime` themselves before calling create.
107
+ if (!options.editor.runtime) options.editor.runtime = runtime
108
+ // Default livesync on for runner mode unless the project disabled it.
109
+ if (runtime === 'runner' && typeof options.editor.livesync === 'undefined') {
110
+ options.editor.livesync = true
111
+ }
112
+ }
113
+
84
114
  export const create = (
85
115
  App,
86
116
  options = DEFAULT_CREATE_OPTIONS,
@@ -91,6 +121,11 @@ export const create = (
91
121
  ...mergeWithLocalFile(options, optionsExternalFile)
92
122
  }
93
123
 
124
+ // Pick up runner-mode (or any future runtime tag) from the served HTML.
125
+ // Has to happen before `createDomqlElement` so syncPlugin's beforeCreate
126
+ // sees the stamped editor.runtime.
127
+ stampRuntimeFromMeta(redefinedOptions)
128
+
94
129
  // Resolve the active theme synchronously, before the async init chain,
95
130
  // and write `data-theme` to the scope's root element. This gives the
96
131
  // first paint the correct theme — no project-side setAttribute hack,
@@ -151,6 +186,12 @@ export const createSync = async (
151
186
  ...mergeWithLocalFile(options, optionsExternalFile)
152
187
  }
153
188
 
189
+ // Pick up runner-mode from the served HTML's meta tag — same path
190
+ // `create` uses. Required because projects often boot via createSync
191
+ // (e.g. starter-kit, docs/developers) and the runner needs to detect
192
+ // its mode regardless of which entry point the project chose.
193
+ stampRuntimeFromMeta(redefinedOptions)
194
+
154
195
  // const SYMBOLS_KEY = process.env.SYMBOLS_KEY
155
196
  const key = options.key
156
197
  await fetchSync(key, redefinedOptions)
@@ -195,3 +236,10 @@ export { defaultDefine, createDefine } from './define.js'
195
236
  // Polyglot i18n plugin
196
237
  export { polyglotPlugin, translate, setLang, getActiveLang, getLanguages, loadTranslations, upsertTranslation, initPolyglot, getLocalStateLang } from '@symbo.ls/polyglot'
197
238
  export { polyglotFunctions } from '@symbo.ls/polyglot/functions'
239
+
240
+ // Theme switching — exposed on the IIFE bundle so project code can call
241
+ // `Smbls.changeGlobalTheme('dark')` from within serialized handlers
242
+ // (mermaid SSR runtime). Without an explicit re-export here, the bundler
243
+ // tree-shakes the symbol out of dist/iife/index.js because nothing in the
244
+ // smbls/src tree calls it directly.
245
+ export { changeGlobalTheme } from '@symbo.ls/scratch'
package/src/prepare.js CHANGED
@@ -18,7 +18,12 @@ import { init } from './init.js'
18
18
  import { getActiveConfig, createConfig, pushConfig, popConfig } from '@symbo.ls/scratch'
19
19
  import { DEFAULT_CONTEXT } from './options.js'
20
20
 
21
- import * as uikit from '@symbo.ls/default-config/components'
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'
22
27
  import * as utils from './utilImports.js'
23
28
  import * as routerUtils from '@symbo.ls/router'
24
29
 
@@ -472,10 +477,15 @@ export const prepareState = (app, context) => {
472
477
  // Merge polyglot translations for active language into state
473
478
  const poly = context.polyglot
474
479
  if (poly?.translations) {
475
- // Check localStorage for persisted language preference
476
- const storedLang = typeof localStorage !== 'undefined'
477
- ? (localStorage.getItem(poly.storageLangKey || 'smbls_lang') || localStorage.getItem('lang'))
478
- : null
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) {}
479
489
  const lang = storedLang || state.lang || poly.defaultLang || 'en'
480
490
  if (storedLang && storedLang !== state.lang) state.lang = storedLang
481
491
  const langT = poly.translations[lang]
package/src/syncExtend.js CHANGED
@@ -1,50 +1,17 @@
1
1
  'use strict'
2
2
 
3
- import { isObjectLike, isUndefined, isDevelopment, isArray } from '@symbo.ls/utils'
4
- import { SyncComponent, Inspect, Notifications } from '@symbo.ls/sync'
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.
5
14
 
6
15
  export const initializeExtend = (app, ctx) => {
7
16
  return isObjectLike(app.extends) ? app.extends : []
8
17
  }
9
-
10
- export const initializeSync = (app, ctx) => {
11
- const { editor } = ctx
12
- if (!editor) return
13
- const liveSync = isUndefined(editor.liveSync) ? isDevelopment() : editor.liveSync
14
- if (liveSync) {
15
- if (isArray(app.extends)) app.extends.push(SyncComponent)
16
- else if (app.extends) {
17
- app.extends = [app.extends, SyncComponent]
18
- } else {
19
- app.extends = [SyncComponent]
20
- }
21
- }
22
- }
23
-
24
- export const initializeInspect = (app, ctx) => {
25
- const { editor } = ctx
26
- if (!editor) return
27
- const inspect = isUndefined(editor.inspect) ? isDevelopment() : editor.inspect
28
- if (inspect) {
29
- if (isArray(app.extends)) app.extends.push(Inspect)
30
- else if (app.extends) {
31
- app.extends = [app.extends, Inspect]
32
- } else {
33
- app.extends = [Inspect]
34
- }
35
- }
36
- }
37
-
38
- export const initializeNotifications = (app, ctx) => {
39
- const { editor } = ctx
40
- if (!editor) return
41
- const verbose = isUndefined(editor.verbose) ? isDevelopment() || ctx.verbose : editor.verbose
42
- if (verbose) {
43
- if (isArray(app.extends)) app.extends.push(Notifications)
44
- else if (app.extends) {
45
- app.extends = [app.extends, Notifications]
46
- } else {
47
- app.extends = [Notifications]
48
- }
49
- }
50
- }