@vielzeug/ore 26.11.9 → 26.11.11

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 (118) hide show
  1. package/dist/base-element.cjs +1 -1
  2. package/dist/base-element.cjs.map +1 -1
  3. package/dist/base-element.d.ts +12 -39
  4. package/dist/base-element.d.ts.map +1 -1
  5. package/dist/base-element.js +1 -1
  6. package/dist/base-element.js.map +1 -1
  7. package/dist/context.cjs.map +1 -1
  8. package/dist/context.js.map +1 -1
  9. package/dist/define.cjs +1 -1
  10. package/dist/define.cjs.map +1 -1
  11. package/dist/define.d.ts.map +1 -1
  12. package/dist/define.js +1 -1
  13. package/dist/define.js.map +1 -1
  14. package/dist/directives/each.cjs +1 -1
  15. package/dist/directives/each.cjs.map +1 -1
  16. package/dist/directives/each.d.ts.map +1 -1
  17. package/dist/directives/each.js +1 -1
  18. package/dist/directives/each.js.map +1 -1
  19. package/dist/directives/model.cjs.map +1 -1
  20. package/dist/directives/model.d.ts +2 -3
  21. package/dist/directives/model.d.ts.map +1 -1
  22. package/dist/directives/model.js.map +1 -1
  23. package/dist/directives/unsafe-html.cjs +1 -1
  24. package/dist/directives/unsafe-html.cjs.map +1 -1
  25. package/dist/directives/unsafe-html.d.ts.map +1 -1
  26. package/dist/directives/unsafe-html.js +1 -1
  27. package/dist/directives/unsafe-html.js.map +1 -1
  28. package/dist/directives/when.cjs +1 -1
  29. package/dist/directives/when.cjs.map +1 -1
  30. package/dist/directives/when.d.ts.map +1 -1
  31. package/dist/directives/when.js +1 -1
  32. package/dist/directives/when.js.map +1 -1
  33. package/dist/errors.cjs +1 -1
  34. package/dist/errors.cjs.map +1 -1
  35. package/dist/errors.d.ts +2 -0
  36. package/dist/errors.d.ts.map +1 -1
  37. package/dist/errors.js +1 -1
  38. package/dist/errors.js.map +1 -1
  39. package/dist/host-bind.cjs +1 -1
  40. package/dist/host-bind.cjs.map +1 -1
  41. package/dist/host-bind.d.ts +2 -2
  42. package/dist/host-bind.js +1 -1
  43. package/dist/host-bind.js.map +1 -1
  44. package/dist/index.cjs +1 -1
  45. package/dist/index.d.ts +0 -1
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +1 -1
  48. package/dist/ore.cjs +2 -2
  49. package/dist/ore.cjs.map +1 -1
  50. package/dist/ore.iife.js +2 -2
  51. package/dist/ore.iife.js.map +1 -1
  52. package/dist/ore.js +411 -424
  53. package/dist/ore.js.map +1 -1
  54. package/dist/props.cjs +1 -1
  55. package/dist/props.cjs.map +1 -1
  56. package/dist/props.d.ts +6 -10
  57. package/dist/props.d.ts.map +1 -1
  58. package/dist/props.js +1 -1
  59. package/dist/props.js.map +1 -1
  60. package/dist/runtime.cjs +1 -1
  61. package/dist/runtime.cjs.map +1 -1
  62. package/dist/runtime.d.ts +22 -9
  63. package/dist/runtime.d.ts.map +1 -1
  64. package/dist/runtime.js +1 -1
  65. package/dist/runtime.js.map +1 -1
  66. package/dist/slots.cjs.map +1 -1
  67. package/dist/slots.d.ts.map +1 -1
  68. package/dist/slots.js.map +1 -1
  69. package/dist/template/binding-types.d.ts +3 -9
  70. package/dist/template/binding-types.d.ts.map +1 -1
  71. package/dist/template/bindings.cjs +1 -1
  72. package/dist/template/bindings.cjs.map +1 -1
  73. package/dist/template/bindings.d.ts +8 -9
  74. package/dist/template/bindings.d.ts.map +1 -1
  75. package/dist/template/bindings.js +1 -1
  76. package/dist/template/bindings.js.map +1 -1
  77. package/dist/template/instantiator.cjs +1 -1
  78. package/dist/template/instantiator.cjs.map +1 -1
  79. package/dist/template/instantiator.d.ts.map +1 -1
  80. package/dist/template/instantiator.js +1 -1
  81. package/dist/template/instantiator.js.map +1 -1
  82. package/dist/template/result.cjs +1 -1
  83. package/dist/template/result.cjs.map +1 -1
  84. package/dist/template/result.d.ts +10 -16
  85. package/dist/template/result.d.ts.map +1 -1
  86. package/dist/template/result.js +1 -1
  87. package/dist/template/result.js.map +1 -1
  88. package/dist/testing/index.d.ts +1 -1
  89. package/dist/testing/index.d.ts.map +1 -1
  90. package/dist/testing/mount.cjs +1 -1
  91. package/dist/testing/mount.cjs.map +1 -1
  92. package/dist/testing/mount.d.ts.map +1 -1
  93. package/dist/testing/mount.js +1 -1
  94. package/dist/testing/mount.js.map +1 -1
  95. package/dist/testing/render-hook.cjs +1 -1
  96. package/dist/testing/render-hook.cjs.map +1 -1
  97. package/dist/testing/render-hook.d.ts +16 -13
  98. package/dist/testing/render-hook.d.ts.map +1 -1
  99. package/dist/testing/render-hook.js +1 -1
  100. package/dist/testing/render-hook.js.map +1 -1
  101. package/dist/testing.cjs.map +1 -1
  102. package/dist/testing.js.map +1 -1
  103. package/dist/utils/brand.cjs.map +1 -1
  104. package/dist/utils/brand.d.ts +1 -1
  105. package/dist/utils/brand.js.map +1 -1
  106. package/dist/utils/dom.cjs +1 -1
  107. package/dist/utils/dom.cjs.map +1 -1
  108. package/dist/utils/dom.d.ts +12 -10
  109. package/dist/utils/dom.d.ts.map +1 -1
  110. package/dist/utils/dom.js +1 -1
  111. package/dist/utils/dom.js.map +1 -1
  112. package/package.json +5 -5
  113. package/dist/directives/live.cjs +0 -2
  114. package/dist/directives/live.cjs.map +0 -1
  115. package/dist/directives/live.d.ts +0 -23
  116. package/dist/directives/live.d.ts.map +0 -1
  117. package/dist/directives/live.js +0 -2
  118. package/dist/directives/live.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"dom.cjs","names":["isReactive","ORE_ERRORS","require_errors","_vielzeug_ripple","resolveMaybeReactive","value","UNSAFE_CSS_CHARS","sanitizeCssToken","runAll","i","fns","removeNodes","node","nodes","createReplaceableSlot","cleanups","fn","next","URL_ATTRS","DANGEROUS_SCHEME_RE","URL_REMOVE_RE","URL_TRIM_RE","normalizeUrlForSchemeCheck","isDangerousUrl","attrWritePlans","ATTR_WRITE_PLAN_LIMIT","getAttrWritePlan","cached","name","lowerName","plan","setAttr","el","val","strVal","listen","listener","handler","options","toKebab","str","c","cssDeclarationName","cssDeclarationValue","resolved","resolveClassTokens","tokens","key","record","token","isStructuredValue"],"sources":["../../src/utils/dom.ts"],"sourcesContent":["/**\n * Low-level DOM utilities used throughout the runtime and binding layers.\n */\n\nimport { isReactive, type Readable } from '@vielzeug/ripple';\n\nimport { warn } from '../_dev';\nimport { ORE_ERRORS } from '../errors';\n\n/**\n * A value that may be a plain value, a reactive signal, or a getter function :\n * the shape `when()`, `each()`, `classMap()`, and `bind()` accept.\n * Exported so consumers can annotate their own composable parameters with it.\n */\nexport type MaybeReactive<T> = T | Readable<T> | (() => T);\n\n/**\n * Resolves a value that may be a plain value, a getter function, or a reactive\n * signal: the one shared three-way branch used by `classMap`/`styleMap`/`bind()`.\n */\nexport const resolveMaybeReactive = <T>(value: MaybeReactive<T>): T =>\n typeof value === 'function' ? (value as () => T)() : isReactive(value) ? value.value : value;\n\n/**\n * Characters that can break out of a CSS declaration in an inline style value or property name.\n * Semicolons end the current declaration; braces are meaningful in stylesheet rules but not\n * inline style values, and signal an injection attempt there.\n */\nexport const UNSAFE_CSS_CHARS = /[;{}]/g;\n\nexport const sanitizeCssToken = (value: string): string => value.replace(UNSAFE_CSS_CHARS, '');\n\nexport const runAll = (fns: (() => void)[]): void => {\n for (let i = fns.length - 1; i >= 0; i--) fns[i]?.();\n};\n\nexport const removeNodes = (nodes: Node[]): void => {\n for (const node of nodes) {\n (node as ChildNode).remove();\n }\n};\n\n/**\n * Tracks \"whatever is currently rendered in one spot\": a list of live DOM nodes plus the\n * cleanup functions that were registered while mounting them. `clear()` tears both down and\n * resets to empty, ready for the next render.\n *\n * Every directive/binding that swaps its rendered content when a reactive source changes\n * (`when()`, `unsafeHtml()`, `each()`'s empty-list fallback, `applyHtmlBinding()`) needs exactly this\n * bookkeeping: this is the one shared implementation instead of four independently-maintained\n * `currentNodes`/`currentCleanups` variable pairs.\n */\nexport type ReplaceableSlot = {\n /** Tears down every registered cleanup and removes every tracked node, then resets to empty. */\n clear(): void;\n /** Currently tracked nodes: read after mounting to know what's live. */\n readonly nodes: Node[];\n /** Pass as the `registerCleanup` callback to whatever mounts the next render. */\n registerCleanup(fn: () => void): void;\n /** Replace the tracked node list (call once mounting the next render is complete). */\n setNodes(nodes: Node[]): void;\n};\n\nexport const createReplaceableSlot = (): ReplaceableSlot => {\n let nodes: Node[] = [];\n let cleanups: (() => void)[] = [];\n\n return {\n clear() {\n runAll(cleanups);\n removeNodes(nodes);\n cleanups = [];\n nodes = [];\n },\n get nodes() {\n return nodes;\n },\n registerCleanup(fn) {\n cleanups.push(fn);\n },\n setNodes(next) {\n nodes = next;\n },\n };\n};\n\n/**\n * HTML attributes that accept URLs. Values bound to these attributes are\n * checked for dangerous schemes before being set.\n *\n * `srcdoc` is deliberately excluded: it holds raw HTML (not a URL), so scheme\n * checking doesn't apply: it's blocked unconditionally below, alongside `on*`.\n */\nconst URL_ATTRS = new Set([\n 'action',\n 'cite',\n 'codebase',\n 'data',\n 'formaction',\n 'href',\n 'manifest',\n 'ping',\n 'poster',\n 'src',\n 'xlink:href',\n]);\n\n/**\n * Schemes that execute JavaScript or can embed arbitrary HTML. Blocked\n * unconditionally in URL-accepting attributes.\n * Covers: javascript:, vbscript:, blob:, and data: variants that carry HTML/XML\n * or script-capable SVG. Plain data: image URIs (e.g. data:image/png) are\n * intentionally allowed.\n */\nconst DANGEROUS_SCHEME_RE =\n /^\\s*(?:(?:javascript|vbscript|blob):|data:(?:[^,]*\\/(?:html|svg\\+xml)|application\\/(?:xhtml|xml)))/i;\n\n/**\n * Control characters and spaces, normalized away before the scheme check to\n * mirror the WHATWG URL parser browsers use when resolving URL attributes: it\n * removes every ASCII tab/LF/CR anywhere in the input and trims C0 controls and\n * spaces from both ends. So `java\\tscript:alert(1)` executes even though the raw\n * string does not start with `javascript:`. Testing the normalized form closes\n * that gap while leaving benign URLs (e.g. one with an internal space) untouched.\n */\nconst URL_REMOVE_RE = /[\\t\\n\\r]/g;\nconst URL_TRIM_RE = /^[\\s\\p{C}]+|[\\s\\p{C}]+$/gu;\n\nconst normalizeUrlForSchemeCheck = (value: string): string => value.replace(URL_REMOVE_RE, '').replace(URL_TRIM_RE, '');\n\nconst isDangerousUrl = (value: string): boolean => DANGEROUS_SCHEME_RE.test(normalizeUrlForSchemeCheck(value));\n\n/**\n * The name-only classification `setAttr` branches on. Attribute names repeat\n * constantly on hot update paths (a template binds `class`/`href`/... once and\n * writes them on every signal change), so the regex/toLowerCase/set-lookup\n * pass runs once per name and every write just reads the cached plan.\n */\ntype AttrWritePlan = {\n event: boolean;\n srcdoc: boolean;\n url: boolean;\n};\n\nconst attrWritePlans = new Map<string, AttrWritePlan>();\nconst ATTR_WRITE_PLAN_LIMIT = 512;\n\nconst getAttrWritePlan = (name: string): AttrWritePlan => {\n const cached = attrWritePlans.get(name);\n\n if (cached) return cached;\n\n const lowerName = name.toLowerCase();\n const plan: AttrWritePlan = {\n event: /^on[a-z]/i.test(name),\n srcdoc: lowerName === 'srcdoc',\n url: URL_ATTRS.has(lowerName),\n };\n\n if (attrWritePlans.size >= ATTR_WRITE_PLAN_LIMIT) attrWritePlans.clear();\n\n attrWritePlans.set(name, plan);\n\n return plan;\n};\n\nexport const setAttr = (el: Element, name: string, val: unknown): void => {\n const plan = getAttrWritePlan(name);\n\n if (plan.event) {\n warn(\n `Blocked setAttribute(\"${name}\", ...): inline event handler attributes are not supported. Use @${name.slice(2)} binding syntax instead.`,\n );\n el.removeAttribute(name);\n\n return;\n }\n\n if (plan.srcdoc) {\n warn(\n `Blocked setAttribute(\"srcdoc\", ...): \"srcdoc\" holds raw HTML, not a URL, and is not supported via attribute binding. Sanitize untrusted content, then use unsafeHtml() if HTML injection is required.`,\n );\n el.removeAttribute(name);\n\n return;\n }\n\n if (val == null || val === false) {\n el.removeAttribute(name);\n\n return;\n }\n\n const strVal = val === true ? 'true' : String(val);\n\n if (plan.url && isDangerousUrl(strVal)) {\n warn(\n `Blocked dangerous URL scheme in attribute \"${name}\". Only safe URLs are permitted in URL-accepting attributes.`,\n );\n el.removeAttribute(name);\n\n return;\n }\n\n el.setAttribute(name, strVal);\n};\n\nexport const listen = (\n el: EventTarget | null | undefined,\n name: string,\n handler: EventListener,\n options?: AddEventListenerOptions,\n): (() => void) => {\n if (!el) {\n warn(ORE_ERRORS.listenNullTarget(name));\n\n return () => {};\n }\n\n const listener: EventListener = handler;\n\n el.addEventListener(name, listener, options);\n\n return () => el.removeEventListener(name, listener, options);\n};\n\nexport const toKebab = (str: string): string => str.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n\n/**\n * Normalize a CSS property name for use in a declaration (camelCase → kebab-case,\n * injection characters stripped). Custom properties (`--*`) are case-sensitive, so\n * they skip the kebab conversion. Shared by `styleMap()` (template side) and\n * `bind({ style })` (host side) so both sanitize identically.\n */\nexport const cssDeclarationName = (name: string): string =>\n sanitizeCssToken(name.startsWith('--') ? name : toKebab(name));\n\n/**\n * Resolve a style value to its declaration text ('' for null/false/empty).\n * Shared by `styleMap()` and `bind({ style })`.\n */\nexport const cssDeclarationValue = (value: unknown): string => {\n const resolved = resolveMaybeReactive(value as MaybeReactive<string | number | null | undefined | false>);\n\n if (resolved == null || resolved === false) return '';\n\n return sanitizeCssToken(String(resolved));\n};\n\n/**\n * Resolve a class map to the set of active class tokens: keys with truthy\n * (possibly reactive) values, whitespace stripped from each key (spaces would\n * inject extra class tokens). Shared by `classMap()` (joins the set into a\n * class attribute) and `bind({ class })` (diffs the set against classList).\n */\nexport const resolveClassTokens = (record: Record<string, MaybeReactive<boolean>>): Set<string> => {\n const tokens = new Set<string>();\n\n for (const [key, value] of Object.entries(record)) {\n if (!resolveMaybeReactive(value)) continue;\n\n const token = key.replace(/\\s+/g, '');\n\n if (token) tokens.add(token);\n }\n\n return tokens;\n};\n\nexport const isStructuredValue = (value: unknown): value is object =>\n Array.isArray(value) || (typeof value === 'object' && value !== null);\n"],"mappings":"A,0FAoBA,IAAaI,EAA2B,GACtC,OAAOC,GAAU,WAAcA,EAAkB,GAAA,EAAIL,EAAAA,WAAAA,CAAWK,CAAK,EAAIA,EAAM,MAAQA,EAO5EC,EAAmB,SAEnBC,EAAoB,GAA0BF,EAAM,QAAQC,EAAkB,EAAE,EAEhFE,EAAU,GAA8B,CACnD,IAAK,IAAIC,EAAIC,EAAI,OAAS,EAAGD,GAAK,EAAG,IAAKC,EAAID,EAAE,GAAG,CACrD,EAEaE,EAAe,GAAwB,CAClD,IAAK,IAAMC,KAAQC,EACjBD,EAAoB,OAAO,CAE/B,EAuBaE,MAA+C,CAC1D,IAAID,EAAgB,CAAC,EACjBE,EAA2B,CAAC,EAEhC,MAAO,CACL,OAAQ,CACNP,EAAOO,CAAQ,EACfJ,EAAYE,CAAK,EACjB,EAAW,CAAC,EACZ,EAAQ,CAAC,CACX,EACA,IAAI,OAAQ,CACV,OAAOA,CACT,EACA,gBAAgB,EAAI,CAClBE,EAAS,KAAKC,CAAE,CAClB,EACA,SAAS,EAAM,CACb,EAAQC,CACV,CACF,CACF,EASMC,EAAY,IAAI,IAAI,CACxB,SACA,OACA,WACA,OACA,aACA,OACA,WACA,OACA,SACA,MACA,YACF,CAAC,EASKC,EACJ,sGAUIC,EAAgB,YAChBC,EAAc,4BAEdC,EAA8B,GAA0BjB,EAAM,QAAQe,EAAe,EAAE,CAAC,CAAC,QAAQC,EAAa,EAAE,EAEhHE,EAAkB,GAA2BJ,EAAoB,KAAKG,EAA2BjB,CAAK,CAAC,EAcvGmB,EAAiB,IAAI,IACrBC,EAAwB,IAExBC,EAAoB,GAAgC,CACxD,IAAMC,EAASH,EAAe,IAAII,CAAI,EAEtC,GAAID,EAAQ,OAAOA,EAEnB,IAAME,EAAYD,EAAK,YAAY,EAC7BE,EAAsB,CAC1B,MAAO,YAAY,KAAKF,CAAI,EAC5B,OAAQC,IAAc,SACtB,IAAKX,EAAU,IAAIW,CAAS,CAC9B,EAMA,OAJIL,EAAe,MAAQC,GAAuBD,EAAe,MAAM,EAEvEA,EAAe,IAAII,EAAME,CAAI,EAEtBA,CACT,EAEaC,GAAW,EAAa,EAAc,IAAuB,CACxE,IAAMD,EAAOJ,EAAiBE,CAAI,EAElC,GAAIE,EAAK,MAAO,CAEZ,GAAyBF,EAAzB,EAAiGA,EAAK,MAAM,CAAC,EAA7G,EAEFI,EAAG,gBAAgBJ,CAAI,EAEvB,MACF,CAWA,GATIE,EAAK,QASLG,GAAO,MAAQA,IAAQ,GAAO,CAChCD,EAAG,gBAAgBJ,CAAI,EAEvB,MACF,CAEA,IAAMM,EAASD,IAAQ,GAAO,OAAS,OAAOA,CAAG,EAE7CH,EAAK,KAAOP,EAAeW,CAAM,GAEjC,GAA8CN,EAA9C,EAEFI,EAAG,gBAAgBJ,CAAI,GAKzBI,EAAG,aAAaJ,EAAMM,CAAM,CAC9B,EAEaC,GACX,EACA,EACA,EACA,IACiB,CACjB,GAAI,CAACH,EAGH,OAFK/B,EAAAA,WAAW,iBAAiB2B,CAAI,MAExB,CAAC,EAGhB,IAAMQ,EAA0BC,EAIhC,OAFAL,EAAG,iBAAiBJ,EAAMQ,EAAUE,CAAO,MAE9BN,EAAG,oBAAoBJ,EAAMQ,EAAUE,CAAO,CAC7D,EAEaC,EAAW,GAAwBC,EAAI,QAAQ,SAAW,GAAM,IAAIC,EAAE,YAAY,GAAG,EAQrFC,EAAsB,GACjCnC,EAAiBqB,EAAK,WAAW,IAAI,EAAIA,EAAOW,EAAQX,CAAI,CAAC,EAMlDe,EAAuB,GAA2B,CAC7D,IAAMC,EAAWxC,EAAqBC,CAAkE,EAIxG,OAFIuC,GAAY,MAAQA,IAAa,GAAc,GAE5CrC,EAAiB,OAAOqC,CAAQ,CAAC,CAC1C,EAQaC,EAAsB,GAAgE,CACjG,IAAMC,EAAS,IAAI,IAEnB,IAAK,GAAM,CAACC,EAAK1C,KAAU,OAAO,QAAQ2C,CAAM,EAAG,CACjD,GAAI,CAAC5C,EAAqBC,CAAK,EAAG,SAElC,IAAM4C,EAAQF,EAAI,QAAQ,OAAQ,EAAE,EAEhCE,GAAOH,EAAO,IAAIG,CAAK,CAC7B,CAEA,OAAOH,CACT,EAEaI,EAAqB,GAChC,MAAM,QAAQ7C,CAAK,GAAM,OAAOA,GAAU,YAAYA,E"}
1
+ {"version":3,"file":"dom.cjs","names":["isReactive","createScope","ORE_ERRORS","require_errors","_vielzeug_ripple","resolveMaybeReactive","value","UNSAFE_CSS_CHARS","sanitizeCssToken","removeNodes","node","nodes","createReplaceableSlot","scope","clear","next","fn","error","nextNodes","URL_ATTRS","DANGEROUS_SCHEME_RE","URL_REMOVE_RE","URL_TRIM_RE","normalizeUrlForSchemeCheck","isDangerousUrl","attrWritePlans","ATTR_WRITE_PLAN_LIMIT","getAttrWritePlan","cached","name","lowerName","plan","setAttr","el","val","strVal","listen","listener","handler","options","toKebab","str","c","cssDeclarationName","cssDeclarationValue","resolved","resolveClassTokens","tokens","key","record","token","isStructuredValue"],"sources":["../../src/utils/dom.ts"],"sourcesContent":["/**\n * Low-level DOM utilities used throughout the runtime and binding layers.\n */\n\nimport { createScope, isReactive, type Readable, type Scope } from '@vielzeug/ripple';\n\nimport { warn } from '../_dev';\nimport { ORE_ERRORS } from '../errors';\n\n/**\n * A value that may be a plain value, a reactive signal, or a getter function :\n * the shape `when()`, `each()`, `classMap()`, and `bind()` accept.\n * Exported so consumers can annotate their own composable parameters with it.\n */\nexport type MaybeReactive<T> = T | Readable<T> | (() => T);\n\n/**\n * Resolves a value that may be a plain value, a getter function, or a reactive\n * signal: the one shared three-way branch used by `classMap`/`styleMap`/`bind()`.\n */\nexport const resolveMaybeReactive = <T>(value: MaybeReactive<T>): T =>\n typeof value === 'function' ? (value as () => T)() : isReactive(value) ? value.value : value;\n\n/**\n * Characters that can break out of a CSS declaration in an inline style value or property name.\n * Semicolons end the current declaration; braces are meaningful in stylesheet rules but not\n * inline style values, and signal an injection attempt there.\n */\nexport const UNSAFE_CSS_CHARS = /[;{}]/g;\n\nexport const sanitizeCssToken = (value: string): string => value.replace(UNSAFE_CSS_CHARS, '');\n\nexport const removeNodes = (nodes: Node[]): void => {\n for (const node of nodes) {\n (node as ChildNode).remove();\n }\n};\n\n/**\n * Tracks \"whatever is currently rendered in one spot\": a list of live DOM nodes plus the\n * ripple scope that owns the effects and `onCleanup` callbacks registered while mounting\n * them. `clear()` disposes that scope and removes the nodes.\n *\n * Every directive/binding that swaps its rendered content when a reactive source changes\n * (`when()`, `unsafeHtml()`, `applyHtmlBinding()`) mounts the next render inside `render()`,\n * so nested effects belong to this slot rather than to whichever effect happens to be\n * running. `scope.run()` clears the transient effect-run owner.\n */\nexport type ReplaceableSlot = {\n /** Dispose the current render scope and remove every tracked node. */\n clear(): void;\n /** Currently tracked nodes. */\n readonly nodes: Node[];\n /**\n * Dispose the previous render, then run `fn` inside a fresh scope. Call\n * `setNodes` from `fn` once mounting finishes.\n */\n render<T>(fn: () => T): T;\n /** Replace the tracked node list (call once mounting the next render is complete). */\n setNodes(nodes: Node[]): void;\n};\n\nexport const createReplaceableSlot = (): ReplaceableSlot => {\n let nodes: Node[] = [];\n let scope: Scope | null = null;\n\n const clear = (): void => {\n scope?.dispose();\n scope = null;\n removeNodes(nodes);\n nodes = [];\n };\n\n return {\n clear,\n get nodes() {\n return nodes;\n },\n render(fn) {\n clear();\n\n const next = createScope();\n\n scope = next;\n\n try {\n return next.run(fn);\n } catch (error) {\n next.dispose();\n scope = null;\n throw error;\n }\n },\n setNodes(nextNodes) {\n nodes = nextNodes;\n },\n };\n};\n\n/**\n * HTML attributes that accept URLs. Values bound to these attributes are\n * checked for dangerous schemes before being set.\n *\n * `srcdoc` is deliberately excluded: it holds raw HTML (not a URL), so scheme\n * checking doesn't apply: it's blocked unconditionally below, alongside `on*`.\n */\nconst URL_ATTRS = new Set([\n 'action',\n 'cite',\n 'codebase',\n 'data',\n 'formaction',\n 'href',\n 'manifest',\n 'ping',\n 'poster',\n 'src',\n 'xlink:href',\n]);\n\n/**\n * Schemes that execute JavaScript or can embed arbitrary HTML. Blocked\n * unconditionally in URL-accepting attributes.\n * Covers: javascript:, vbscript:, blob:, and data: variants that carry HTML/XML\n * or script-capable SVG. Plain data: image URIs (e.g. data:image/png) are\n * intentionally allowed.\n */\nconst DANGEROUS_SCHEME_RE =\n /^\\s*(?:(?:javascript|vbscript|blob):|data:(?:[^,]*\\/(?:html|svg\\+xml)|application\\/(?:xhtml|xml)))/i;\n\n/**\n * Control characters and spaces, normalized away before the scheme check to\n * mirror the WHATWG URL parser browsers use when resolving URL attributes: it\n * removes every ASCII tab/LF/CR anywhere in the input and trims C0 controls and\n * spaces from both ends. So `java\\tscript:alert(1)` executes even though the raw\n * string does not start with `javascript:`. Testing the normalized form closes\n * that gap while leaving benign URLs (e.g. one with an internal space) untouched.\n */\nconst URL_REMOVE_RE = /[\\t\\n\\r]/g;\nconst URL_TRIM_RE = /^[\\s\\p{C}]+|[\\s\\p{C}]+$/gu;\n\nconst normalizeUrlForSchemeCheck = (value: string): string => value.replace(URL_REMOVE_RE, '').replace(URL_TRIM_RE, '');\n\nconst isDangerousUrl = (value: string): boolean => DANGEROUS_SCHEME_RE.test(normalizeUrlForSchemeCheck(value));\n\n/**\n * The name-only classification `setAttr` branches on. Attribute names repeat\n * constantly on hot update paths (a template binds `class`/`href`/... once and\n * writes them on every signal change), so the regex/toLowerCase/set-lookup\n * pass runs once per name and every write just reads the cached plan.\n */\ntype AttrWritePlan = {\n event: boolean;\n srcdoc: boolean;\n url: boolean;\n};\n\nconst attrWritePlans = new Map<string, AttrWritePlan>();\nconst ATTR_WRITE_PLAN_LIMIT = 512;\n\nconst getAttrWritePlan = (name: string): AttrWritePlan => {\n const cached = attrWritePlans.get(name);\n\n if (cached) return cached;\n\n const lowerName = name.toLowerCase();\n const plan: AttrWritePlan = {\n event: /^on[a-z]/i.test(name),\n srcdoc: lowerName === 'srcdoc',\n url: URL_ATTRS.has(lowerName),\n };\n\n if (attrWritePlans.size >= ATTR_WRITE_PLAN_LIMIT) attrWritePlans.clear();\n\n attrWritePlans.set(name, plan);\n\n return plan;\n};\n\nexport const setAttr = (el: Element, name: string, val: unknown): void => {\n const plan = getAttrWritePlan(name);\n\n if (plan.event) {\n warn(\n `Blocked setAttribute(\"${name}\", ...): inline event handler attributes are not supported. Use @${name.slice(2)} binding syntax instead.`,\n );\n el.removeAttribute(name);\n\n return;\n }\n\n if (plan.srcdoc) {\n warn(\n `Blocked setAttribute(\"srcdoc\", ...): \"srcdoc\" holds raw HTML, not a URL, and is not supported via attribute binding. Sanitize untrusted content, then use unsafeHtml() if HTML injection is required.`,\n );\n el.removeAttribute(name);\n\n return;\n }\n\n if (val == null || val === false) {\n el.removeAttribute(name);\n\n return;\n }\n\n const strVal = val === true ? 'true' : String(val);\n\n if (plan.url && isDangerousUrl(strVal)) {\n warn(\n `Blocked dangerous URL scheme in attribute \"${name}\". Only safe URLs are permitted in URL-accepting attributes.`,\n );\n el.removeAttribute(name);\n\n return;\n }\n\n el.setAttribute(name, strVal);\n};\n\nexport const listen = (\n el: EventTarget | null | undefined,\n name: string,\n handler: EventListener,\n options?: AddEventListenerOptions,\n): (() => void) => {\n if (!el) {\n warn(ORE_ERRORS.listenNullTarget(name));\n\n return () => {};\n }\n\n const listener: EventListener = handler;\n\n el.addEventListener(name, listener, options);\n\n return () => el.removeEventListener(name, listener, options);\n};\n\nexport const toKebab = (str: string): string => str.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n\n/**\n * Normalize a CSS property name for use in a declaration (camelCase → kebab-case,\n * injection characters stripped). Custom properties (`--*`) are case-sensitive, so\n * they skip the kebab conversion. Shared by `styleMap()` (template side) and\n * `bind({ style })` (host side) so both sanitize identically.\n */\nexport const cssDeclarationName = (name: string): string =>\n sanitizeCssToken(name.startsWith('--') ? name : toKebab(name));\n\n/**\n * Resolve a style value to its declaration text ('' for null/false/empty).\n * Shared by `styleMap()` and `bind({ style })`.\n */\nexport const cssDeclarationValue = (value: unknown): string => {\n const resolved = resolveMaybeReactive(value as MaybeReactive<string | number | null | undefined | false>);\n\n if (resolved == null || resolved === false) return '';\n\n return sanitizeCssToken(String(resolved));\n};\n\n/**\n * Resolve a class map to the set of active class tokens: keys with truthy\n * (possibly reactive) values, whitespace stripped from each key (spaces would\n * inject extra class tokens). Shared by `classMap()` (joins the set into a\n * class attribute) and `bind({ class })` (diffs the set against classList).\n */\nexport const resolveClassTokens = (record: Record<string, MaybeReactive<boolean>>): Set<string> => {\n const tokens = new Set<string>();\n\n for (const [key, value] of Object.entries(record)) {\n if (!resolveMaybeReactive(value)) continue;\n\n const token = key.replace(/\\s+/g, '');\n\n if (token) tokens.add(token);\n }\n\n return tokens;\n};\n\nexport const isStructuredValue = (value: unknown): value is object =>\n Array.isArray(value) || (typeof value === 'object' && value !== null);\n"],"mappings":"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"}
@@ -20,25 +20,27 @@ export declare const resolveMaybeReactive: <T>(value: MaybeReactive<T>) => T;
20
20
  */
21
21
  export declare const UNSAFE_CSS_CHARS: RegExp;
22
22
  export declare const sanitizeCssToken: (value: string) => string;
23
- export declare const runAll: (fns: (() => void)[]) => void;
24
23
  export declare const removeNodes: (nodes: Node[]) => void;
25
24
  /**
26
25
  * Tracks "whatever is currently rendered in one spot": a list of live DOM nodes plus the
27
- * cleanup functions that were registered while mounting them. `clear()` tears both down and
28
- * resets to empty, ready for the next render.
26
+ * ripple scope that owns the effects and `onCleanup` callbacks registered while mounting
27
+ * them. `clear()` disposes that scope and removes the nodes.
29
28
  *
30
29
  * Every directive/binding that swaps its rendered content when a reactive source changes
31
- * (`when()`, `unsafeHtml()`, `each()`'s empty-list fallback, `applyHtmlBinding()`) needs exactly this
32
- * bookkeeping: this is the one shared implementation instead of four independently-maintained
33
- * `currentNodes`/`currentCleanups` variable pairs.
30
+ * (`when()`, `unsafeHtml()`, `applyHtmlBinding()`) mounts the next render inside `render()`,
31
+ * so nested effects belong to this slot rather than to whichever effect happens to be
32
+ * running. `scope.run()` clears the transient effect-run owner.
34
33
  */
35
34
  export type ReplaceableSlot = {
36
- /** Tears down every registered cleanup and removes every tracked node, then resets to empty. */
35
+ /** Dispose the current render scope and remove every tracked node. */
37
36
  clear(): void;
38
- /** Currently tracked nodes: read after mounting to know what's live. */
37
+ /** Currently tracked nodes. */
39
38
  readonly nodes: Node[];
40
- /** Pass as the `registerCleanup` callback to whatever mounts the next render. */
41
- registerCleanup(fn: () => void): void;
39
+ /**
40
+ * Dispose the previous render, then run `fn` inside a fresh scope. Call
41
+ * `setNodes` from `fn` once mounting finishes.
42
+ */
43
+ render<T>(fn: () => T): T;
42
44
  /** Replace the tracked node list (call once mounting the next render is complete). */
43
45
  setNodes(nodes: Node[]): void;
44
46
  };
@@ -1 +1 @@
1
- {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/utils/dom.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAc,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAK7D;;;;GAIG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AAE3D;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,EAAE,OAAO,aAAa,CAAC,CAAC,CAAC,KAAG,CAC4B,CAAC;AAE/F;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAAW,CAAC;AAEzC,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,KAAG,MAA6C,CAAC;AAE/F,eAAO,MAAM,MAAM,GAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,KAAG,IAE5C,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,OAAO,IAAI,EAAE,KAAG,IAI3C,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,gGAAgG;IAChG,KAAK,IAAI,IAAI,CAAC;IACd,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC;IACvB,iFAAiF;IACjF,eAAe,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACtC,sFAAsF;IACtF,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAO,eAqBxC,CAAC;AAkFF,eAAO,MAAM,OAAO,GAAI,IAAI,OAAO,EAAE,MAAM,MAAM,EAAE,KAAK,OAAO,KAAG,IAuCjE,CAAC;AAEF,eAAO,MAAM,MAAM,GACjB,IAAI,WAAW,GAAG,IAAI,GAAG,SAAS,EAClC,MAAM,MAAM,EACZ,SAAS,aAAa,EACtB,UAAU,uBAAuB,KAChC,CAAC,MAAM,IAAI,CAYb,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,KAAG,MAA6D,CAAC;AAEpG;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,MACc,CAAC;AAEjE;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,OAAO,KAAG,MAMpD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC,KAAG,GAAG,CAAC,MAAM,CAY7F,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,MACW,CAAC"}
1
+ {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/utils/dom.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAA2B,KAAK,QAAQ,EAAc,MAAM,kBAAkB,CAAC;AAKtF;;;;GAIG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AAE3D;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,EAAE,OAAO,aAAa,CAAC,CAAC,CAAC,KAAG,CAC4B,CAAC;AAE/F;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAAW,CAAC;AAEzC,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,KAAG,MAA6C,CAAC;AAE/F,eAAO,MAAM,WAAW,GAAI,OAAO,IAAI,EAAE,KAAG,IAI3C,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,sEAAsE;IACtE,KAAK,IAAI,IAAI,CAAC;IACd,+BAA+B;IAC/B,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC;IACvB;;;OAGG;IACH,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1B,sFAAsF;IACtF,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAO,eAmCxC,CAAC;AAkFF,eAAO,MAAM,OAAO,GAAI,IAAI,OAAO,EAAE,MAAM,MAAM,EAAE,KAAK,OAAO,KAAG,IAuCjE,CAAC;AAEF,eAAO,MAAM,MAAM,GACjB,IAAI,WAAW,GAAG,IAAI,GAAG,SAAS,EAClC,MAAM,MAAM,EACZ,SAAS,aAAa,EACtB,UAAU,uBAAuB,KAChC,CAAC,MAAM,IAAI,CAYb,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,KAAG,MAA6D,CAAC;AAEpG;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,MACc,CAAC;AAEjE;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,OAAO,KAAG,MAMpD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC,KAAG,GAAG,CAAC,MAAM,CAY7F,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,MACW,CAAC"}
package/dist/utils/dom.js CHANGED
@@ -1,2 +1,2 @@
1
- import"../_dev.js";import{ORE_ERRORS as e}from"../errors.js";import{isReactive as t}from"@vielzeug/ripple";var n=e=>typeof e==`function`?e():t(e)?e.value:e,r=/[;{}]/g,i=e=>e.replace(r,``),a=e=>{for(let t=e.length-1;t>=0;t--)e[t]?.()},o=e=>{for(let t of e)t.remove()},s=()=>{let e=[],t=[];return{clear(){a(t),o(e),t=[],e=[]},get nodes(){return e},registerCleanup(e){t.push(e)},setNodes(t){e=t}}},c=/* @__PURE__ */ new Set([`action`,`cite`,`codebase`,`data`,`formaction`,`href`,`manifest`,`ping`,`poster`,`src`,`xlink:href`]),l=/^\s*(?:(?:javascript|vbscript|blob):|data:(?:[^,]*\/(?:html|svg\+xml)|application\/(?:xhtml|xml)))/i,u=/[\t\n\r]/g,d=/^[\s\p{C}]+|[\s\p{C}]+$/gu,f=e=>e.replace(u,``).replace(d,``),p=e=>l.test(f(e)),m=/* @__PURE__ */ new Map,h=512,g=e=>{let t=m.get(e);if(t)return t;let n=e.toLowerCase(),r={event:/^on[a-z]/i.test(e),srcdoc:n===`srcdoc`,url:c.has(n)};return m.size>=h&&m.clear(),m.set(e,r),r},_=(e,t,n)=>{let r=g(t);if(r.event){`${t}${t.slice(2)}`,e.removeAttribute(t);return}if(r.srcdoc||n==null||n===!1){e.removeAttribute(t);return}let i=n===!0?`true`:String(n);r.url&&p(i)?(`${t}`,e.removeAttribute(t)):e.setAttribute(t,i)},v=(t,n,r,i)=>{if(!t)return e.listenNullTarget(n),()=>{};let a=r;return t.addEventListener(n,a,i),()=>t.removeEventListener(n,a,i)},y=e=>e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`),b=e=>i(e.startsWith(`--`)?e:y(e)),x=e=>{let t=n(e);return t==null||t===!1?``:i(String(t))},S=e=>{let t=/* @__PURE__ */ new Set;for(let[r,i]of Object.entries(e)){if(!n(i))continue;let e=r.replace(/\s+/g,``);e&&t.add(e)}return t},C=e=>Array.isArray(e)||typeof e==`object`&&!!e;export{r as UNSAFE_CSS_CHARS,s as createReplaceableSlot,b as cssDeclarationName,x as cssDeclarationValue,C as isStructuredValue,v as listen,o as removeNodes,S as resolveClassTokens,n as resolveMaybeReactive,a as runAll,i as sanitizeCssToken,_ as setAttr,y as toKebab};
1
+ import"../_dev.js";import{ORE_ERRORS as e}from"../errors.js";import{createScope as t,isReactive as n}from"@vielzeug/ripple";var r=e=>typeof e==`function`?e():n(e)?e.value:e,i=/[;{}]/g,a=e=>e.replace(i,``),o=e=>{for(let t of e)t.remove()},s=()=>{let e=[],n=null,r=()=>{n?.dispose(),n=null,o(e),e=[]};return{clear:r,get nodes(){return e},render(e){r();let i=t();n=i;try{return i.run(e)}catch(e){throw i.dispose(),n=null,e}},setNodes(t){e=t}}},c=/* @__PURE__ */ new Set([`action`,`cite`,`codebase`,`data`,`formaction`,`href`,`manifest`,`ping`,`poster`,`src`,`xlink:href`]),l=/^\s*(?:(?:javascript|vbscript|blob):|data:(?:[^,]*\/(?:html|svg\+xml)|application\/(?:xhtml|xml)))/i,u=/[\t\n\r]/g,d=/^[\s\p{C}]+|[\s\p{C}]+$/gu,f=e=>e.replace(u,``).replace(d,``),p=e=>l.test(f(e)),m=/* @__PURE__ */ new Map,h=512,g=e=>{let t=m.get(e);if(t)return t;let n=e.toLowerCase(),r={event:/^on[a-z]/i.test(e),srcdoc:n===`srcdoc`,url:c.has(n)};return m.size>=h&&m.clear(),m.set(e,r),r},_=(e,t,n)=>{let r=g(t);if(r.event){`${t}${t.slice(2)}`,e.removeAttribute(t);return}if(r.srcdoc||n==null||n===!1){e.removeAttribute(t);return}let i=n===!0?`true`:String(n);r.url&&p(i)?(`${t}`,e.removeAttribute(t)):e.setAttribute(t,i)},v=(t,n,r,i)=>{if(!t)return e.listenNullTarget(n),()=>{};let a=r;return t.addEventListener(n,a,i),()=>t.removeEventListener(n,a,i)},y=e=>e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`),b=e=>a(e.startsWith(`--`)?e:y(e)),x=e=>{let t=r(e);return t==null||t===!1?``:a(String(t))},S=e=>{let t=/* @__PURE__ */ new Set;for(let[n,i]of Object.entries(e)){if(!r(i))continue;let e=n.replace(/\s+/g,``);e&&t.add(e)}return t},C=e=>Array.isArray(e)||typeof e==`object`&&!!e;export{i as UNSAFE_CSS_CHARS,s as createReplaceableSlot,b as cssDeclarationName,x as cssDeclarationValue,C as isStructuredValue,v as listen,o as removeNodes,S as resolveClassTokens,r as resolveMaybeReactive,a as sanitizeCssToken,_ as setAttr,y as toKebab};
2
2
  //# sourceMappingURL=dom.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"dom.js","names":["resolveMaybeReactive","value","isReactive","UNSAFE_CSS_CHARS","sanitizeCssToken","runAll","i","fns","removeNodes","node","nodes","createReplaceableSlot","cleanups","fn","next","URL_ATTRS","DANGEROUS_SCHEME_RE","URL_REMOVE_RE","URL_TRIM_RE","normalizeUrlForSchemeCheck","isDangerousUrl","attrWritePlans","ATTR_WRITE_PLAN_LIMIT","getAttrWritePlan","cached","name","lowerName","plan","setAttr","el","val","strVal","listen","ORE_ERRORS","listener","handler","options","toKebab","str","c","cssDeclarationName","cssDeclarationValue","resolved","resolveClassTokens","tokens","key","record","token","isStructuredValue"],"sources":["../../src/utils/dom.ts"],"sourcesContent":["/**\n * Low-level DOM utilities used throughout the runtime and binding layers.\n */\n\nimport { isReactive, type Readable } from '@vielzeug/ripple';\n\nimport { warn } from '../_dev';\nimport { ORE_ERRORS } from '../errors';\n\n/**\n * A value that may be a plain value, a reactive signal, or a getter function :\n * the shape `when()`, `each()`, `classMap()`, and `bind()` accept.\n * Exported so consumers can annotate their own composable parameters with it.\n */\nexport type MaybeReactive<T> = T | Readable<T> | (() => T);\n\n/**\n * Resolves a value that may be a plain value, a getter function, or a reactive\n * signal: the one shared three-way branch used by `classMap`/`styleMap`/`bind()`.\n */\nexport const resolveMaybeReactive = <T>(value: MaybeReactive<T>): T =>\n typeof value === 'function' ? (value as () => T)() : isReactive(value) ? value.value : value;\n\n/**\n * Characters that can break out of a CSS declaration in an inline style value or property name.\n * Semicolons end the current declaration; braces are meaningful in stylesheet rules but not\n * inline style values, and signal an injection attempt there.\n */\nexport const UNSAFE_CSS_CHARS = /[;{}]/g;\n\nexport const sanitizeCssToken = (value: string): string => value.replace(UNSAFE_CSS_CHARS, '');\n\nexport const runAll = (fns: (() => void)[]): void => {\n for (let i = fns.length - 1; i >= 0; i--) fns[i]?.();\n};\n\nexport const removeNodes = (nodes: Node[]): void => {\n for (const node of nodes) {\n (node as ChildNode).remove();\n }\n};\n\n/**\n * Tracks \"whatever is currently rendered in one spot\": a list of live DOM nodes plus the\n * cleanup functions that were registered while mounting them. `clear()` tears both down and\n * resets to empty, ready for the next render.\n *\n * Every directive/binding that swaps its rendered content when a reactive source changes\n * (`when()`, `unsafeHtml()`, `each()`'s empty-list fallback, `applyHtmlBinding()`) needs exactly this\n * bookkeeping: this is the one shared implementation instead of four independently-maintained\n * `currentNodes`/`currentCleanups` variable pairs.\n */\nexport type ReplaceableSlot = {\n /** Tears down every registered cleanup and removes every tracked node, then resets to empty. */\n clear(): void;\n /** Currently tracked nodes: read after mounting to know what's live. */\n readonly nodes: Node[];\n /** Pass as the `registerCleanup` callback to whatever mounts the next render. */\n registerCleanup(fn: () => void): void;\n /** Replace the tracked node list (call once mounting the next render is complete). */\n setNodes(nodes: Node[]): void;\n};\n\nexport const createReplaceableSlot = (): ReplaceableSlot => {\n let nodes: Node[] = [];\n let cleanups: (() => void)[] = [];\n\n return {\n clear() {\n runAll(cleanups);\n removeNodes(nodes);\n cleanups = [];\n nodes = [];\n },\n get nodes() {\n return nodes;\n },\n registerCleanup(fn) {\n cleanups.push(fn);\n },\n setNodes(next) {\n nodes = next;\n },\n };\n};\n\n/**\n * HTML attributes that accept URLs. Values bound to these attributes are\n * checked for dangerous schemes before being set.\n *\n * `srcdoc` is deliberately excluded: it holds raw HTML (not a URL), so scheme\n * checking doesn't apply: it's blocked unconditionally below, alongside `on*`.\n */\nconst URL_ATTRS = new Set([\n 'action',\n 'cite',\n 'codebase',\n 'data',\n 'formaction',\n 'href',\n 'manifest',\n 'ping',\n 'poster',\n 'src',\n 'xlink:href',\n]);\n\n/**\n * Schemes that execute JavaScript or can embed arbitrary HTML. Blocked\n * unconditionally in URL-accepting attributes.\n * Covers: javascript:, vbscript:, blob:, and data: variants that carry HTML/XML\n * or script-capable SVG. Plain data: image URIs (e.g. data:image/png) are\n * intentionally allowed.\n */\nconst DANGEROUS_SCHEME_RE =\n /^\\s*(?:(?:javascript|vbscript|blob):|data:(?:[^,]*\\/(?:html|svg\\+xml)|application\\/(?:xhtml|xml)))/i;\n\n/**\n * Control characters and spaces, normalized away before the scheme check to\n * mirror the WHATWG URL parser browsers use when resolving URL attributes: it\n * removes every ASCII tab/LF/CR anywhere in the input and trims C0 controls and\n * spaces from both ends. So `java\\tscript:alert(1)` executes even though the raw\n * string does not start with `javascript:`. Testing the normalized form closes\n * that gap while leaving benign URLs (e.g. one with an internal space) untouched.\n */\nconst URL_REMOVE_RE = /[\\t\\n\\r]/g;\nconst URL_TRIM_RE = /^[\\s\\p{C}]+|[\\s\\p{C}]+$/gu;\n\nconst normalizeUrlForSchemeCheck = (value: string): string => value.replace(URL_REMOVE_RE, '').replace(URL_TRIM_RE, '');\n\nconst isDangerousUrl = (value: string): boolean => DANGEROUS_SCHEME_RE.test(normalizeUrlForSchemeCheck(value));\n\n/**\n * The name-only classification `setAttr` branches on. Attribute names repeat\n * constantly on hot update paths (a template binds `class`/`href`/... once and\n * writes them on every signal change), so the regex/toLowerCase/set-lookup\n * pass runs once per name and every write just reads the cached plan.\n */\ntype AttrWritePlan = {\n event: boolean;\n srcdoc: boolean;\n url: boolean;\n};\n\nconst attrWritePlans = new Map<string, AttrWritePlan>();\nconst ATTR_WRITE_PLAN_LIMIT = 512;\n\nconst getAttrWritePlan = (name: string): AttrWritePlan => {\n const cached = attrWritePlans.get(name);\n\n if (cached) return cached;\n\n const lowerName = name.toLowerCase();\n const plan: AttrWritePlan = {\n event: /^on[a-z]/i.test(name),\n srcdoc: lowerName === 'srcdoc',\n url: URL_ATTRS.has(lowerName),\n };\n\n if (attrWritePlans.size >= ATTR_WRITE_PLAN_LIMIT) attrWritePlans.clear();\n\n attrWritePlans.set(name, plan);\n\n return plan;\n};\n\nexport const setAttr = (el: Element, name: string, val: unknown): void => {\n const plan = getAttrWritePlan(name);\n\n if (plan.event) {\n warn(\n `Blocked setAttribute(\"${name}\", ...): inline event handler attributes are not supported. Use @${name.slice(2)} binding syntax instead.`,\n );\n el.removeAttribute(name);\n\n return;\n }\n\n if (plan.srcdoc) {\n warn(\n `Blocked setAttribute(\"srcdoc\", ...): \"srcdoc\" holds raw HTML, not a URL, and is not supported via attribute binding. Sanitize untrusted content, then use unsafeHtml() if HTML injection is required.`,\n );\n el.removeAttribute(name);\n\n return;\n }\n\n if (val == null || val === false) {\n el.removeAttribute(name);\n\n return;\n }\n\n const strVal = val === true ? 'true' : String(val);\n\n if (plan.url && isDangerousUrl(strVal)) {\n warn(\n `Blocked dangerous URL scheme in attribute \"${name}\". Only safe URLs are permitted in URL-accepting attributes.`,\n );\n el.removeAttribute(name);\n\n return;\n }\n\n el.setAttribute(name, strVal);\n};\n\nexport const listen = (\n el: EventTarget | null | undefined,\n name: string,\n handler: EventListener,\n options?: AddEventListenerOptions,\n): (() => void) => {\n if (!el) {\n warn(ORE_ERRORS.listenNullTarget(name));\n\n return () => {};\n }\n\n const listener: EventListener = handler;\n\n el.addEventListener(name, listener, options);\n\n return () => el.removeEventListener(name, listener, options);\n};\n\nexport const toKebab = (str: string): string => str.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n\n/**\n * Normalize a CSS property name for use in a declaration (camelCase → kebab-case,\n * injection characters stripped). Custom properties (`--*`) are case-sensitive, so\n * they skip the kebab conversion. Shared by `styleMap()` (template side) and\n * `bind({ style })` (host side) so both sanitize identically.\n */\nexport const cssDeclarationName = (name: string): string =>\n sanitizeCssToken(name.startsWith('--') ? name : toKebab(name));\n\n/**\n * Resolve a style value to its declaration text ('' for null/false/empty).\n * Shared by `styleMap()` and `bind({ style })`.\n */\nexport const cssDeclarationValue = (value: unknown): string => {\n const resolved = resolveMaybeReactive(value as MaybeReactive<string | number | null | undefined | false>);\n\n if (resolved == null || resolved === false) return '';\n\n return sanitizeCssToken(String(resolved));\n};\n\n/**\n * Resolve a class map to the set of active class tokens: keys with truthy\n * (possibly reactive) values, whitespace stripped from each key (spaces would\n * inject extra class tokens). Shared by `classMap()` (joins the set into a\n * class attribute) and `bind({ class })` (diffs the set against classList).\n */\nexport const resolveClassTokens = (record: Record<string, MaybeReactive<boolean>>): Set<string> => {\n const tokens = new Set<string>();\n\n for (const [key, value] of Object.entries(record)) {\n if (!resolveMaybeReactive(value)) continue;\n\n const token = key.replace(/\\s+/g, '');\n\n if (token) tokens.add(token);\n }\n\n return tokens;\n};\n\nexport const isStructuredValue = (value: unknown): value is object =>\n Array.isArray(value) || (typeof value === 'object' && value !== null);\n"],"mappings":"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"}
1
+ {"version":3,"file":"dom.js","names":["resolveMaybeReactive","value","isReactive","UNSAFE_CSS_CHARS","sanitizeCssToken","removeNodes","node","nodes","createReplaceableSlot","scope","clear","next","createScope","fn","error","nextNodes","URL_ATTRS","DANGEROUS_SCHEME_RE","URL_REMOVE_RE","URL_TRIM_RE","normalizeUrlForSchemeCheck","isDangerousUrl","attrWritePlans","ATTR_WRITE_PLAN_LIMIT","getAttrWritePlan","cached","name","lowerName","plan","setAttr","el","val","strVal","listen","ORE_ERRORS","listener","handler","options","toKebab","str","c","cssDeclarationName","cssDeclarationValue","resolved","resolveClassTokens","tokens","key","record","token","isStructuredValue"],"sources":["../../src/utils/dom.ts"],"sourcesContent":["/**\n * Low-level DOM utilities used throughout the runtime and binding layers.\n */\n\nimport { createScope, isReactive, type Readable, type Scope } from '@vielzeug/ripple';\n\nimport { warn } from '../_dev';\nimport { ORE_ERRORS } from '../errors';\n\n/**\n * A value that may be a plain value, a reactive signal, or a getter function :\n * the shape `when()`, `each()`, `classMap()`, and `bind()` accept.\n * Exported so consumers can annotate their own composable parameters with it.\n */\nexport type MaybeReactive<T> = T | Readable<T> | (() => T);\n\n/**\n * Resolves a value that may be a plain value, a getter function, or a reactive\n * signal: the one shared three-way branch used by `classMap`/`styleMap`/`bind()`.\n */\nexport const resolveMaybeReactive = <T>(value: MaybeReactive<T>): T =>\n typeof value === 'function' ? (value as () => T)() : isReactive(value) ? value.value : value;\n\n/**\n * Characters that can break out of a CSS declaration in an inline style value or property name.\n * Semicolons end the current declaration; braces are meaningful in stylesheet rules but not\n * inline style values, and signal an injection attempt there.\n */\nexport const UNSAFE_CSS_CHARS = /[;{}]/g;\n\nexport const sanitizeCssToken = (value: string): string => value.replace(UNSAFE_CSS_CHARS, '');\n\nexport const removeNodes = (nodes: Node[]): void => {\n for (const node of nodes) {\n (node as ChildNode).remove();\n }\n};\n\n/**\n * Tracks \"whatever is currently rendered in one spot\": a list of live DOM nodes plus the\n * ripple scope that owns the effects and `onCleanup` callbacks registered while mounting\n * them. `clear()` disposes that scope and removes the nodes.\n *\n * Every directive/binding that swaps its rendered content when a reactive source changes\n * (`when()`, `unsafeHtml()`, `applyHtmlBinding()`) mounts the next render inside `render()`,\n * so nested effects belong to this slot rather than to whichever effect happens to be\n * running. `scope.run()` clears the transient effect-run owner.\n */\nexport type ReplaceableSlot = {\n /** Dispose the current render scope and remove every tracked node. */\n clear(): void;\n /** Currently tracked nodes. */\n readonly nodes: Node[];\n /**\n * Dispose the previous render, then run `fn` inside a fresh scope. Call\n * `setNodes` from `fn` once mounting finishes.\n */\n render<T>(fn: () => T): T;\n /** Replace the tracked node list (call once mounting the next render is complete). */\n setNodes(nodes: Node[]): void;\n};\n\nexport const createReplaceableSlot = (): ReplaceableSlot => {\n let nodes: Node[] = [];\n let scope: Scope | null = null;\n\n const clear = (): void => {\n scope?.dispose();\n scope = null;\n removeNodes(nodes);\n nodes = [];\n };\n\n return {\n clear,\n get nodes() {\n return nodes;\n },\n render(fn) {\n clear();\n\n const next = createScope();\n\n scope = next;\n\n try {\n return next.run(fn);\n } catch (error) {\n next.dispose();\n scope = null;\n throw error;\n }\n },\n setNodes(nextNodes) {\n nodes = nextNodes;\n },\n };\n};\n\n/**\n * HTML attributes that accept URLs. Values bound to these attributes are\n * checked for dangerous schemes before being set.\n *\n * `srcdoc` is deliberately excluded: it holds raw HTML (not a URL), so scheme\n * checking doesn't apply: it's blocked unconditionally below, alongside `on*`.\n */\nconst URL_ATTRS = new Set([\n 'action',\n 'cite',\n 'codebase',\n 'data',\n 'formaction',\n 'href',\n 'manifest',\n 'ping',\n 'poster',\n 'src',\n 'xlink:href',\n]);\n\n/**\n * Schemes that execute JavaScript or can embed arbitrary HTML. Blocked\n * unconditionally in URL-accepting attributes.\n * Covers: javascript:, vbscript:, blob:, and data: variants that carry HTML/XML\n * or script-capable SVG. Plain data: image URIs (e.g. data:image/png) are\n * intentionally allowed.\n */\nconst DANGEROUS_SCHEME_RE =\n /^\\s*(?:(?:javascript|vbscript|blob):|data:(?:[^,]*\\/(?:html|svg\\+xml)|application\\/(?:xhtml|xml)))/i;\n\n/**\n * Control characters and spaces, normalized away before the scheme check to\n * mirror the WHATWG URL parser browsers use when resolving URL attributes: it\n * removes every ASCII tab/LF/CR anywhere in the input and trims C0 controls and\n * spaces from both ends. So `java\\tscript:alert(1)` executes even though the raw\n * string does not start with `javascript:`. Testing the normalized form closes\n * that gap while leaving benign URLs (e.g. one with an internal space) untouched.\n */\nconst URL_REMOVE_RE = /[\\t\\n\\r]/g;\nconst URL_TRIM_RE = /^[\\s\\p{C}]+|[\\s\\p{C}]+$/gu;\n\nconst normalizeUrlForSchemeCheck = (value: string): string => value.replace(URL_REMOVE_RE, '').replace(URL_TRIM_RE, '');\n\nconst isDangerousUrl = (value: string): boolean => DANGEROUS_SCHEME_RE.test(normalizeUrlForSchemeCheck(value));\n\n/**\n * The name-only classification `setAttr` branches on. Attribute names repeat\n * constantly on hot update paths (a template binds `class`/`href`/... once and\n * writes them on every signal change), so the regex/toLowerCase/set-lookup\n * pass runs once per name and every write just reads the cached plan.\n */\ntype AttrWritePlan = {\n event: boolean;\n srcdoc: boolean;\n url: boolean;\n};\n\nconst attrWritePlans = new Map<string, AttrWritePlan>();\nconst ATTR_WRITE_PLAN_LIMIT = 512;\n\nconst getAttrWritePlan = (name: string): AttrWritePlan => {\n const cached = attrWritePlans.get(name);\n\n if (cached) return cached;\n\n const lowerName = name.toLowerCase();\n const plan: AttrWritePlan = {\n event: /^on[a-z]/i.test(name),\n srcdoc: lowerName === 'srcdoc',\n url: URL_ATTRS.has(lowerName),\n };\n\n if (attrWritePlans.size >= ATTR_WRITE_PLAN_LIMIT) attrWritePlans.clear();\n\n attrWritePlans.set(name, plan);\n\n return plan;\n};\n\nexport const setAttr = (el: Element, name: string, val: unknown): void => {\n const plan = getAttrWritePlan(name);\n\n if (plan.event) {\n warn(\n `Blocked setAttribute(\"${name}\", ...): inline event handler attributes are not supported. Use @${name.slice(2)} binding syntax instead.`,\n );\n el.removeAttribute(name);\n\n return;\n }\n\n if (plan.srcdoc) {\n warn(\n `Blocked setAttribute(\"srcdoc\", ...): \"srcdoc\" holds raw HTML, not a URL, and is not supported via attribute binding. Sanitize untrusted content, then use unsafeHtml() if HTML injection is required.`,\n );\n el.removeAttribute(name);\n\n return;\n }\n\n if (val == null || val === false) {\n el.removeAttribute(name);\n\n return;\n }\n\n const strVal = val === true ? 'true' : String(val);\n\n if (plan.url && isDangerousUrl(strVal)) {\n warn(\n `Blocked dangerous URL scheme in attribute \"${name}\". Only safe URLs are permitted in URL-accepting attributes.`,\n );\n el.removeAttribute(name);\n\n return;\n }\n\n el.setAttribute(name, strVal);\n};\n\nexport const listen = (\n el: EventTarget | null | undefined,\n name: string,\n handler: EventListener,\n options?: AddEventListenerOptions,\n): (() => void) => {\n if (!el) {\n warn(ORE_ERRORS.listenNullTarget(name));\n\n return () => {};\n }\n\n const listener: EventListener = handler;\n\n el.addEventListener(name, listener, options);\n\n return () => el.removeEventListener(name, listener, options);\n};\n\nexport const toKebab = (str: string): string => str.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n\n/**\n * Normalize a CSS property name for use in a declaration (camelCase → kebab-case,\n * injection characters stripped). Custom properties (`--*`) are case-sensitive, so\n * they skip the kebab conversion. Shared by `styleMap()` (template side) and\n * `bind({ style })` (host side) so both sanitize identically.\n */\nexport const cssDeclarationName = (name: string): string =>\n sanitizeCssToken(name.startsWith('--') ? name : toKebab(name));\n\n/**\n * Resolve a style value to its declaration text ('' for null/false/empty).\n * Shared by `styleMap()` and `bind({ style })`.\n */\nexport const cssDeclarationValue = (value: unknown): string => {\n const resolved = resolveMaybeReactive(value as MaybeReactive<string | number | null | undefined | false>);\n\n if (resolved == null || resolved === false) return '';\n\n return sanitizeCssToken(String(resolved));\n};\n\n/**\n * Resolve a class map to the set of active class tokens: keys with truthy\n * (possibly reactive) values, whitespace stripped from each key (spaces would\n * inject extra class tokens). Shared by `classMap()` (joins the set into a\n * class attribute) and `bind({ class })` (diffs the set against classList).\n */\nexport const resolveClassTokens = (record: Record<string, MaybeReactive<boolean>>): Set<string> => {\n const tokens = new Set<string>();\n\n for (const [key, value] of Object.entries(record)) {\n if (!resolveMaybeReactive(value)) continue;\n\n const token = key.replace(/\\s+/g, '');\n\n if (token) tokens.add(token);\n }\n\n return tokens;\n};\n\nexport const isStructuredValue = (value: unknown): value is object =>\n Array.isArray(value) || (typeof value === 'object' && value !== null);\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielzeug/ore",
3
- "version": "26.11.9",
3
+ "version": "26.11.11",
4
4
  "description": "Functional web-component authoring on top of ripple",
5
5
  "repository": {
6
6
  "type": "git",
@@ -54,8 +54,8 @@
54
54
  "test:types": "tsc -p tsconfig.json --noEmit"
55
55
  },
56
56
  "peerDependencies": {
57
- "@vielzeug/assay": "^26.11.9",
58
- "@vielzeug/ripple": "^26.11.9"
57
+ "@vielzeug/assay": "^26.11.11",
58
+ "@vielzeug/ripple": "^26.11.11"
59
59
  },
60
60
  "peerDependenciesMeta": {
61
61
  "@vielzeug/assay": {
@@ -64,8 +64,8 @@
64
64
  },
65
65
  "devDependencies": {
66
66
  "@types/node": "^26.6.6",
67
- "@vielzeug/assay": "26.11.9",
68
- "@vielzeug/ripple": "26.11.9",
67
+ "@vielzeug/assay": "26.11.11",
68
+ "@vielzeug/ripple": "26.11.11",
69
69
  "axe-core": "^4.14.0",
70
70
  "jsdom": "^31.0.0",
71
71
  "typescript": "^6.0.3",
@@ -1,2 +0,0 @@
1
- var e=require("../utils/brand.cjs").makeBrand(`ore:live`),t=t=>e.stamp({source:t}),n=e.is;exports.isLiveBinding=n,exports.live=t;
2
- //# sourceMappingURL=live.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"live.cjs","names":["makeBrand","liveBrand","live","isLiveBinding"],"sources":["../../src/directives/live.ts"],"sourcesContent":["import type { Readable } from '@vielzeug/ripple';\n\nimport { makeBrand } from '../utils/brand';\n\n/**\n * A per-binding marker produced by `live()`. Wraps the source signal: the\n * \"live\" flag belongs to this one binding site, never to the signal itself,\n * so other bindings of the same signal are unaffected.\n *\n * @example\n * html`<input value=\"${live(model)}\" />`\n */\nexport type LiveBinding<T> = { readonly source: Readable<T> };\n\nconst liveBrand = makeBrand<LiveBinding<unknown>>('ore:live');\n\n/**\n * Marks one attribute binding as \"live\" so stale app-state writes never clobber\n * in-progress user input at that binding site.\n *\n * For form controls: if the current DOM value diverges from the last write made\n * by this binding, subsequent app-state writes are silently dropped until the\n * DOM value matches the incoming value or no prior write has been recorded.\n */\nexport const live = <T>(source: Readable<T>): LiveBinding<T> => liveBrand.stamp({ source }) as LiveBinding<T>;\n\nexport const isLiveBinding = liveBrand.is;\n"],"mappings":"AAcA,IAAMC,E,4BAAYD,CAAAA,CAAAA,UAAgC,UAAU,EAU/CE,EAAW,GAAwCD,EAAU,MAAM,CAAE,QAAO,CAAC,EAE7EE,EAAgBF,EAAU,G"}
@@ -1,23 +0,0 @@
1
- import type { Readable } from '@vielzeug/ripple';
2
- /**
3
- * A per-binding marker produced by `live()`. Wraps the source signal: the
4
- * "live" flag belongs to this one binding site, never to the signal itself,
5
- * so other bindings of the same signal are unaffected.
6
- *
7
- * @example
8
- * html`<input value="${live(model)}" />`
9
- */
10
- export type LiveBinding<T> = {
11
- readonly source: Readable<T>;
12
- };
13
- /**
14
- * Marks one attribute binding as "live" so stale app-state writes never clobber
15
- * in-progress user input at that binding site.
16
- *
17
- * For form controls: if the current DOM value diverges from the last write made
18
- * by this binding, subsequent app-state writes are silently dropped until the
19
- * DOM value matches the incoming value or no prior write has been recorded.
20
- */
21
- export declare const live: <T>(source: Readable<T>) => LiveBinding<T>;
22
- export declare const isLiveBinding: (value: unknown) => value is LiveBinding<unknown>;
23
- //# sourceMappingURL=live.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../src/directives/live.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAIjD;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAA;CAAE,CAAC;AAI9D;;;;;;;GAOG;AACH,eAAO,MAAM,IAAI,GAAI,CAAC,EAAE,QAAQ,QAAQ,CAAC,CAAC,CAAC,KAAG,WAAW,CAAC,CAAC,CAAkD,CAAC;AAE9G,eAAO,MAAM,aAAa,mDAAe,CAAC"}
@@ -1,2 +0,0 @@
1
- import{makeBrand as e}from"../utils/brand.js";var t=e(`ore:live`),n=e=>t.stamp({source:e}),r=t.is;export{r as isLiveBinding,n as live};
2
- //# sourceMappingURL=live.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"live.js","names":["liveBrand","makeBrand","live","isLiveBinding"],"sources":["../../src/directives/live.ts"],"sourcesContent":["import type { Readable } from '@vielzeug/ripple';\n\nimport { makeBrand } from '../utils/brand';\n\n/**\n * A per-binding marker produced by `live()`. Wraps the source signal: the\n * \"live\" flag belongs to this one binding site, never to the signal itself,\n * so other bindings of the same signal are unaffected.\n *\n * @example\n * html`<input value=\"${live(model)}\" />`\n */\nexport type LiveBinding<T> = { readonly source: Readable<T> };\n\nconst liveBrand = makeBrand<LiveBinding<unknown>>('ore:live');\n\n/**\n * Marks one attribute binding as \"live\" so stale app-state writes never clobber\n * in-progress user input at that binding site.\n *\n * For form controls: if the current DOM value diverges from the last write made\n * by this binding, subsequent app-state writes are silently dropped until the\n * DOM value matches the incoming value or no prior write has been recorded.\n */\nexport const live = <T>(source: Readable<T>): LiveBinding<T> => liveBrand.stamp({ source }) as LiveBinding<T>;\n\nexport const isLiveBinding = liveBrand.is;\n"],"mappings":"A,8CAcA,IAAMA,EAAYC,EAAgC,UAAU,EAU/CC,EAAW,GAAwCF,EAAU,MAAM,CAAE,QAAO,CAAC,EAE7EG,EAAgBH,EAAU,G"}