@favish/staffbase-utils 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/html.cjs.js +1 -1
  2. package/dist/html.cjs.js.map +1 -1
  3. package/dist/html.es.mjs +15 -10
  4. package/dist/html.es.mjs.map +1 -1
  5. package/dist/renderWidgets-CeIczubt.mjs +81 -0
  6. package/dist/renderWidgets-CeIczubt.mjs.map +1 -0
  7. package/dist/renderWidgets-EbJq9Wn6.js +2 -0
  8. package/dist/renderWidgets-EbJq9Wn6.js.map +1 -0
  9. package/dist/src/html/customElementHandling.d.ts +12 -0
  10. package/dist/src/html/customElementHandling.d.ts.map +1 -0
  11. package/dist/src/html/sanitizeArticleHtml.d.ts.map +1 -1
  12. package/dist/src/html/sanitizeHtml.d.ts +3 -1
  13. package/dist/src/html/sanitizeHtml.d.ts.map +1 -1
  14. package/dist/src/types/widgets/RenderWidgetsOptions.d.ts +21 -0
  15. package/dist/src/types/widgets/RenderWidgetsOptions.d.ts.map +1 -0
  16. package/dist/src/types/widgets/RenderWidgetsResult.d.ts +13 -0
  17. package/dist/src/types/widgets/RenderWidgetsResult.d.ts.map +1 -0
  18. package/dist/src/types/widgets/StaffbaseWidgetManagerConstructor.d.ts +9 -0
  19. package/dist/src/types/widgets/StaffbaseWidgetManagerConstructor.d.ts.map +1 -0
  20. package/dist/src/types/widgets/StaffbaseWidgetManagerPrototype.d.ts +11 -0
  21. package/dist/src/types/widgets/StaffbaseWidgetManagerPrototype.d.ts.map +1 -0
  22. package/dist/src/types/widgets/UseRenderWidgetsOptions.d.ts +16 -0
  23. package/dist/src/types/widgets/UseRenderWidgetsOptions.d.ts.map +1 -0
  24. package/dist/src/widgets/defaultWidgetOnError.d.ts +10 -0
  25. package/dist/src/widgets/defaultWidgetOnError.d.ts.map +1 -0
  26. package/dist/src/widgets/getWidgetManagerConstructor.d.ts +8 -0
  27. package/dist/src/widgets/getWidgetManagerConstructor.d.ts.map +1 -0
  28. package/dist/src/widgets/getWidgetManagerPrototype.d.ts +8 -0
  29. package/dist/src/widgets/getWidgetManagerPrototype.d.ts.map +1 -0
  30. package/dist/src/widgets/hasRequiredWidgetManagerMethods.d.ts +12 -0
  31. package/dist/src/widgets/hasRequiredWidgetManagerMethods.d.ts.map +1 -0
  32. package/dist/src/widgets/index.d.ts +2 -0
  33. package/dist/src/widgets/index.d.ts.map +1 -0
  34. package/dist/src/widgets/isKnownStaffbaseRenderError.d.ts +9 -0
  35. package/dist/src/widgets/isKnownStaffbaseRenderError.d.ts.map +1 -0
  36. package/dist/src/widgets/react/index.d.ts +2 -0
  37. package/dist/src/widgets/react/index.d.ts.map +1 -0
  38. package/dist/src/widgets/react/useRenderWidgets.d.ts +14 -0
  39. package/dist/src/widgets/react/useRenderWidgets.d.ts.map +1 -0
  40. package/dist/src/widgets/renderWidgets.d.ts +16 -0
  41. package/dist/src/widgets/renderWidgets.d.ts.map +1 -0
  42. package/dist/widgets/react.cjs.js +2 -0
  43. package/dist/widgets/react.cjs.js.map +1 -0
  44. package/dist/widgets/react.es.mjs +28 -0
  45. package/dist/widgets/react.es.mjs.map +1 -0
  46. package/dist/widgets.cjs.js +1 -0
  47. package/dist/widgets.es.mjs +2 -0
  48. package/package.json +12 -1
package/dist/html.cjs.js CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require("dompurify");c=s(c);var l=e=>{if(!e)return``;try{return c.default.sanitize(e,{ALLOWED_TAGS:[],KEEP_CONTENT:!0})}catch{return e.replace(/<\/?[^>]+(>|$)/g,``)}},u=e=>l(e).toLowerCase().replace(/[.,!?;:"'()[\]\-_/\\]/g,` `).replace(/\s+/g,` `).trim(),d=(0,c.default)(window),f=null;d.addHook(`afterSanitizeAttributes`,e=>{if(e instanceof Element&&(e.tagName===`A`&&e.getAttribute(`target`)===`_blank`&&e.setAttribute(`rel`,`noopener noreferrer`),e.tagName===`IFRAME`&&f)){let t=e.getAttribute(`src`)??``;f(t)||e.parentNode?.removeChild(e)}});var p=(e,t={})=>{f=t.isAllowedIframeSrc??null;try{return d.sanitize(e,{USE_PROFILES:{html:!0},ADD_TAGS:[`iframe`],ADD_ATTR:[`target`,`allow`,`allowfullscreen`,`frameborder`,`scrolling`,`loading`,`referrerpolicy`],FORBID_TAGS:[`script`,`style`],FORBID_ATTR:[`onerror`,`onload`,`onclick`]})}finally{f=null}},m=e=>c.default.sanitize(e);exports.cleanHTML=u,exports.sanitizeArticleHtml=p,exports.sanitizeHtml=m,exports.stripHtmlTags=l;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require("dompurify");c=s(c);var l=e=>{if(!e)return``;try{return c.default.sanitize(e,{ALLOWED_TAGS:[],KEEP_CONTENT:!0})}catch{return e.replace(/<\/?[^>]+(>|$)/g,``)}},u=e=>l(e).toLowerCase().replace(/[.,!?;:"'()[\]\-_/\\]/g,` `).replace(/\s+/g,` `).trim(),d={tagNameCheck:/^[a-z][a-z0-9]*-[a-z0-9-]*$/,attributeNameCheck:/^(?!on)[a-z][a-z0-9]*([-:][a-z0-9]+)*$/,allowCustomizedBuiltInElements:!0},f=(0,c.default)(window),p=null;f.addHook(`afterSanitizeAttributes`,e=>{if(e instanceof Element&&(e.tagName===`A`&&e.getAttribute(`target`)===`_blank`&&e.setAttribute(`rel`,`noopener noreferrer`),e.tagName===`IFRAME`&&p)){let t=e.getAttribute(`src`)??``;p(t)||e.parentNode?.removeChild(e)}});var m=(e,t={})=>{p=t.isAllowedIframeSrc??null;try{return f.sanitize(e,{USE_PROFILES:{html:!0},CUSTOM_ELEMENT_HANDLING:d,ADD_TAGS:[`iframe`],ADD_ATTR:[`target`,`allow`,`allowfullscreen`,`frameborder`,`scrolling`,`loading`,`referrerpolicy`],FORBID_TAGS:[`script`,`style`],FORBID_ATTR:[`onerror`,`onload`,`onclick`]})}finally{p=null}},h=e=>c.default.sanitize(e,{CUSTOM_ELEMENT_HANDLING:d});exports.cleanHTML=u,exports.sanitizeArticleHtml=m,exports.sanitizeHtml=h,exports.stripHtmlTags=l;
2
2
  //# sourceMappingURL=html.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"html.cjs.js","names":[],"sources":["../src/html/stripHtmlTags.ts","../src/html/cleanHTML.ts","../src/html/sanitizeArticleHtml.ts","../src/html/sanitizeHtml.ts"],"sourcesContent":["import DOMPurify from 'dompurify'\n\n/**\n * Strips all HTML tags, keeping the text content. From smart-search's\n * stripHtmlTags; its html-react-parser fallback is intentionally dropped to\n * avoid a heavy runtime dependency — the DOMPurify path plus a regex fallback is\n * sufficient and never throws in practice.\n * @param {string | undefined} html - String that may contain HTML tags.\n * @returns {string} The text content with tags removed.\n */\nexport const stripHtmlTags = (html?: string): string => {\n if (!html) return ''\n\n try {\n return DOMPurify.sanitize(html, { ALLOWED_TAGS: [], KEEP_CONTENT: true })\n } catch {\n return html.replace(/<\\/?[^>]+(>|$)/g, '')\n }\n}\n","import { stripHtmlTags } from './stripHtmlTags'\n\n/**\n * Normalizes HTML into a lowercase, punctuation-free, single-spaced token string\n * for search indexing/matching. From alerts' cleanHTML; builds on stripHtmlTags\n * so tag removal stays single-sourced.\n * @param {string} html - The HTML string to clean.\n * @returns {string} The cleaned, normalized text.\n */\nexport const cleanHTML = (html: string): string =>\n stripHtmlTags(html)\n .toLowerCase()\n .replace(/[.,!?;:\"'()[\\]\\-_/\\\\]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n","import createDOMPurify from 'dompurify'\n\nimport type { SanitizeArticleHtmlOptions } from '../types/html/SanitizeArticleHtmlOptions'\n\n// Dedicated DOMPurify instance so the hardening hook below is scoped to this\n// sanitizer and never pollutes the consumer's shared default DOMPurify instance\n// (other code may sanitize through the default instance directly).\nconst purifier = createDOMPurify(window)\n\n// Set synchronously around each (synchronous) sanitize call. JS is\n// single-threaded, so the guard cannot leak across calls.\nlet activeIframeGuard: ((src: string) => boolean) | null = null\n\n// afterSanitizeAttributes hook: force rel=\"noopener noreferrer\" on\n// target=\"_blank\" anchors (reverse-tabnabbing), and drop iframes whose src fails\n// the injected allowlist predicate when one is active.\npurifier.addHook('afterSanitizeAttributes', (node) => {\n if (!(node instanceof Element)) return\n\n if (node.tagName === 'A' && node.getAttribute('target') === '_blank') {\n node.setAttribute('rel', 'noopener noreferrer')\n }\n\n if (node.tagName === 'IFRAME' && activeIframeGuard) {\n const src = node.getAttribute('src') ?? ''\n if (!activeIframeGuard(src)) node.parentNode?.removeChild(node)\n }\n})\n\n/**\n * Canonical sanitizer for rich article HTML rendered into a session-bearing\n * webview. Superset of the alerts and unacknowledged-bulletins variants: keeps\n * iframes and data-* attributes (renderWidgets discovers embedded sub-widgets via\n * data-*), strips scripts/inline handlers/javascript:, and forces\n * rel=\"noopener noreferrer\" on target=\"_blank\" anchors. When options.isAllowedIframeSrc\n * is provided, iframes whose src fails it are dropped (injected so this module\n * does not depend on /links, which owns isAllowedIframeSrc).\n * @param {string} html - Raw article HTML from the Staffbase API.\n * @param {SanitizeArticleHtmlOptions} options - Optional iframe-src allowlist.\n * @returns {string} Sanitized HTML safe to inject/parse.\n */\nexport const sanitizeArticleHtml = (\n html: string,\n options: SanitizeArticleHtmlOptions = {},\n): string => {\n activeIframeGuard = options.isAllowedIframeSrc ?? null\n try {\n return purifier.sanitize(html, {\n USE_PROFILES: { html: true },\n ADD_TAGS: ['iframe'],\n ADD_ATTR: [\n 'target',\n 'allow',\n 'allowfullscreen',\n 'frameborder',\n 'scrolling',\n 'loading',\n 'referrerpolicy',\n ],\n FORBID_TAGS: ['script', 'style'],\n FORBID_ATTR: ['onerror', 'onload', 'onclick'],\n })\n } finally {\n activeIframeGuard = null\n }\n}\n","import DOMPurify from 'dompurify'\n\n/**\n * Strict sanitizer for untrusted snippet/teaser HTML rendered through a\n * non-sanitizing parser (e.g. html-react-parser). The default DOMPurify profile\n * strips scripts, inline handlers and dangerous URLs AND drops iframes, keeping\n * only basic rich-text markup. From global-content's sanitizeHtml.\n *\n * Use sanitizeArticleHtml instead when rendering full article bodies that must\n * keep iframes / data-* embeds.\n * @param {string} html - Raw HTML string from the API.\n * @returns {string} Sanitized HTML.\n */\nexport const sanitizeHtml = (html: string): string => DOMPurify.sanitize(html)\n"],"mappings":"mkBAUA,IAAa,EAAiB,GAA0B,CACtD,GAAI,CAAC,EAAM,MAAO,GAElB,GAAI,CACF,OAAO,EAAA,QAAU,SAAS,EAAM,CAAE,aAAc,CAAC,EAAG,aAAc,EAAK,CAAC,CAC1E,MAAQ,CACN,OAAO,EAAK,QAAQ,kBAAmB,EAAE,CAC3C,CACF,ECTa,EAAa,GACxB,EAAc,CAAI,EACf,YAAY,EACZ,QAAQ,yBAA0B,GAAG,EACrC,QAAQ,OAAQ,GAAG,EACnB,KAAK,ECPJ,GAAA,EAAA,EAAA,SAA2B,MAAM,EAInC,EAAuD,KAK3D,EAAS,QAAQ,0BAA4B,GAAS,CAC9C,gBAAgB,UAElB,EAAK,UAAY,KAAO,EAAK,aAAa,QAAQ,IAAM,UAC1D,EAAK,aAAa,MAAO,qBAAqB,EAG5C,EAAK,UAAY,UAAY,GAAmB,CAClD,IAAM,EAAM,EAAK,aAAa,KAAK,GAAK,GACnC,EAAkB,CAAG,GAAG,EAAK,YAAY,YAAY,CAAI,CAChE,CACF,CAAC,EAcD,IAAa,GACX,EACA,EAAsC,CAAC,IAC5B,CACX,EAAoB,EAAQ,oBAAsB,KAClD,GAAI,CACF,OAAO,EAAS,SAAS,EAAM,CAC7B,aAAc,CAAE,KAAM,EAAK,EAC3B,SAAU,CAAC,QAAQ,EACnB,SAAU,CACR,SACA,QACA,kBACA,cACA,YACA,UACA,gBACF,EACA,YAAa,CAAC,SAAU,OAAO,EAC/B,YAAa,CAAC,UAAW,SAAU,SAAS,CAC9C,CAAC,CACH,QAAU,CACR,EAAoB,IACtB,CACF,ECpDa,EAAgB,GAAyB,EAAA,QAAU,SAAS,CAAI"}
1
+ {"version":3,"file":"html.cjs.js","names":[],"sources":["../src/html/stripHtmlTags.ts","../src/html/cleanHTML.ts","../src/html/customElementHandling.ts","../src/html/sanitizeArticleHtml.ts","../src/html/sanitizeHtml.ts"],"sourcesContent":["import DOMPurify from 'dompurify'\n\n/**\n * Strips all HTML tags, keeping the text content. From smart-search's\n * stripHtmlTags; its html-react-parser fallback is intentionally dropped to\n * avoid a heavy runtime dependency — the DOMPurify path plus a regex fallback is\n * sufficient and never throws in practice.\n * @param {string | undefined} html - String that may contain HTML tags.\n * @returns {string} The text content with tags removed.\n */\nexport const stripHtmlTags = (html?: string): string => {\n if (!html) return ''\n\n try {\n return DOMPurify.sanitize(html, { ALLOWED_TAGS: [], KEEP_CONTENT: true })\n } catch {\n return html.replace(/<\\/?[^>]+(>|$)/g, '')\n }\n}\n","import { stripHtmlTags } from './stripHtmlTags'\n\n/**\n * Normalizes HTML into a lowercase, punctuation-free, single-spaced token string\n * for search indexing/matching. From alerts' cleanHTML; builds on stripHtmlTags\n * so tag removal stays single-sourced.\n * @param {string} html - The HTML string to clean.\n * @returns {string} The cleaned, normalized text.\n */\nexport const cleanHTML = (html: string): string =>\n stripHtmlTags(html)\n .toLowerCase()\n .replace(/[.,!?;:\"'()[\\]\\-_/\\\\]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n","import type { Config } from 'dompurify'\n\n/**\n * DOMPurify custom-element handling that preserves embedded-widget custom\n * elements (any hyphenated tag, e.g. `<news-teaser>`) and their kebab-case /\n * `data-` attributes so the host widget manager (renderWidgets) can hydrate\n * them. Without this, DOMPurify's default profile silently strips unknown custom\n * elements AND their attributes, wiping embeds out of the content before they can\n * render. `on*` event-handler attributes are still rejected (defense in depth\n * alongside DOMPurify's own XSS stripping). Promoted from staffbase-global-content.\n */\nexport const customElementHandling: NonNullable<\n Config['CUSTOM_ELEMENT_HANDLING']\n> = {\n // Any valid custom-element tag name (must contain a hyphen per the spec).\n tagNameCheck: /^[a-z][a-z0-9]*-[a-z0-9-]*$/,\n // Allow kebab-case / data- attributes on custom elements, but never `on*`.\n attributeNameCheck: /^(?!on)[a-z][a-z0-9]*([-:][a-z0-9]+)*$/,\n // Permit `<div is=\"some-widget\">`-style customized built-in elements.\n allowCustomizedBuiltInElements: true,\n}\n","import createDOMPurify from 'dompurify'\n\nimport type { SanitizeArticleHtmlOptions } from '../types/html/SanitizeArticleHtmlOptions'\nimport { customElementHandling } from './customElementHandling'\n\n// Dedicated DOMPurify instance so the hardening hook below is scoped to this\n// sanitizer and never pollutes the consumer's shared default DOMPurify instance\n// (other code may sanitize through the default instance directly).\nconst purifier = createDOMPurify(window)\n\n// Set synchronously around each (synchronous) sanitize call. JS is\n// single-threaded, so the guard cannot leak across calls.\nlet activeIframeGuard: ((src: string) => boolean) | null = null\n\n// afterSanitizeAttributes hook: force rel=\"noopener noreferrer\" on\n// target=\"_blank\" anchors (reverse-tabnabbing), and drop iframes whose src fails\n// the injected allowlist predicate when one is active.\npurifier.addHook('afterSanitizeAttributes', (node) => {\n if (!(node instanceof Element)) return\n\n if (node.tagName === 'A' && node.getAttribute('target') === '_blank') {\n node.setAttribute('rel', 'noopener noreferrer')\n }\n\n if (node.tagName === 'IFRAME' && activeIframeGuard) {\n const src = node.getAttribute('src') ?? ''\n if (!activeIframeGuard(src)) node.parentNode?.removeChild(node)\n }\n})\n\n/**\n * Canonical sanitizer for rich article HTML rendered into a session-bearing\n * webview. Superset of the alerts and unacknowledged-bulletins variants: keeps\n * iframes and data-* attributes (renderWidgets discovers embedded sub-widgets via\n * data-*), strips scripts/inline handlers/javascript:, and forces\n * rel=\"noopener noreferrer\" on target=\"_blank\" anchors. When options.isAllowedIframeSrc\n * is provided, iframes whose src fails it are dropped (injected so this module\n * does not depend on /links, which owns isAllowedIframeSrc).\n * @param {string} html - Raw article HTML from the Staffbase API.\n * @param {SanitizeArticleHtmlOptions} options - Optional iframe-src allowlist.\n * @returns {string} Sanitized HTML safe to inject/parse.\n */\nexport const sanitizeArticleHtml = (\n html: string,\n options: SanitizeArticleHtmlOptions = {},\n): string => {\n activeIframeGuard = options.isAllowedIframeSrc ?? null\n try {\n return purifier.sanitize(html, {\n USE_PROFILES: { html: true },\n CUSTOM_ELEMENT_HANDLING: customElementHandling,\n ADD_TAGS: ['iframe'],\n ADD_ATTR: [\n 'target',\n 'allow',\n 'allowfullscreen',\n 'frameborder',\n 'scrolling',\n 'loading',\n 'referrerpolicy',\n ],\n FORBID_TAGS: ['script', 'style'],\n FORBID_ATTR: ['onerror', 'onload', 'onclick'],\n })\n } finally {\n activeIframeGuard = null\n }\n}\n","import DOMPurify from 'dompurify'\n\nimport { customElementHandling } from './customElementHandling'\n\n/**\n * Strict sanitizer for untrusted snippet/teaser HTML rendered through a\n * non-sanitizing parser (e.g. html-react-parser). The default DOMPurify profile\n * strips scripts, inline handlers and dangerous URLs AND drops iframes, keeping\n * only basic rich-text markup. Embedded-widget custom elements (and their\n * kebab/data attributes) are preserved so the host can hydrate them. From\n * global-content's sanitizeHtml.\n *\n * Use sanitizeArticleHtml instead when rendering full article bodies that must\n * keep iframes / data-* embeds.\n * @param {string} html - Raw HTML string from the API.\n * @returns {string} Sanitized HTML.\n */\nexport const sanitizeHtml = (html: string): string =>\n DOMPurify.sanitize(html, { CUSTOM_ELEMENT_HANDLING: customElementHandling })\n"],"mappings":"mkBAUA,IAAa,EAAiB,GAA0B,CACtD,GAAI,CAAC,EAAM,MAAO,GAElB,GAAI,CACF,OAAO,EAAA,QAAU,SAAS,EAAM,CAAE,aAAc,CAAC,EAAG,aAAc,EAAK,CAAC,CAC1E,MAAQ,CACN,OAAO,EAAK,QAAQ,kBAAmB,EAAE,CAC3C,CACF,ECTa,EAAa,GACxB,EAAc,CAAI,EACf,YAAY,EACZ,QAAQ,yBAA0B,GAAG,EACrC,QAAQ,OAAQ,GAAG,EACnB,KAAK,ECHG,EAET,CAEF,aAAc,8BAEd,mBAAoB,yCAEpB,+BAAgC,EAClC,ECZM,GAAA,EAAA,EAAA,SAA2B,MAAM,EAInC,EAAuD,KAK3D,EAAS,QAAQ,0BAA4B,GAAS,CAC9C,gBAAgB,UAElB,EAAK,UAAY,KAAO,EAAK,aAAa,QAAQ,IAAM,UAC1D,EAAK,aAAa,MAAO,qBAAqB,EAG5C,EAAK,UAAY,UAAY,GAAmB,CAClD,IAAM,EAAM,EAAK,aAAa,KAAK,GAAK,GACnC,EAAkB,CAAG,GAAG,EAAK,YAAY,YAAY,CAAI,CAChE,CACF,CAAC,EAcD,IAAa,GACX,EACA,EAAsC,CAAC,IAC5B,CACX,EAAoB,EAAQ,oBAAsB,KAClD,GAAI,CACF,OAAO,EAAS,SAAS,EAAM,CAC7B,aAAc,CAAE,KAAM,EAAK,EAC3B,wBAAyB,EACzB,SAAU,CAAC,QAAQ,EACnB,SAAU,CACR,SACA,QACA,kBACA,cACA,YACA,UACA,gBACF,EACA,YAAa,CAAC,SAAU,OAAO,EAC/B,YAAa,CAAC,UAAW,SAAU,SAAS,CAC9C,CAAC,CACH,QAAU,CACR,EAAoB,IACtB,CACF,EClDa,EAAgB,GAC3B,EAAA,QAAU,SAAS,EAAM,CAAE,wBAAyB,CAAsB,CAAC"}
package/dist/html.es.mjs CHANGED
@@ -10,18 +10,23 @@ var t = (t) => {
10
10
  } catch {
11
11
  return t.replace(/<\/?[^>]+(>|$)/g, "");
12
12
  }
13
- }, n = (e) => t(e).toLowerCase().replace(/[.,!?;:"'()[\]\-_/\\]/g, " ").replace(/\s+/g, " ").trim(), r = e(window), i = null;
14
- r.addHook("afterSanitizeAttributes", (e) => {
15
- if (e instanceof Element && (e.tagName === "A" && e.getAttribute("target") === "_blank" && e.setAttribute("rel", "noopener noreferrer"), e.tagName === "IFRAME" && i)) {
13
+ }, n = (e) => t(e).toLowerCase().replace(/[.,!?;:"'()[\]\-_/\\]/g, " ").replace(/\s+/g, " ").trim(), r = {
14
+ tagNameCheck: /^[a-z][a-z0-9]*-[a-z0-9-]*$/,
15
+ attributeNameCheck: /^(?!on)[a-z][a-z0-9]*([-:][a-z0-9]+)*$/,
16
+ allowCustomizedBuiltInElements: !0
17
+ }, i = e(window), a = null;
18
+ i.addHook("afterSanitizeAttributes", (e) => {
19
+ if (e instanceof Element && (e.tagName === "A" && e.getAttribute("target") === "_blank" && e.setAttribute("rel", "noopener noreferrer"), e.tagName === "IFRAME" && a)) {
16
20
  let t = e.getAttribute("src") ?? "";
17
- i(t) || e.parentNode?.removeChild(e);
21
+ a(t) || e.parentNode?.removeChild(e);
18
22
  }
19
23
  });
20
- var a = (e, t = {}) => {
21
- i = t.isAllowedIframeSrc ?? null;
24
+ var o = (e, t = {}) => {
25
+ a = t.isAllowedIframeSrc ?? null;
22
26
  try {
23
- return r.sanitize(e, {
27
+ return i.sanitize(e, {
24
28
  USE_PROFILES: { html: !0 },
29
+ CUSTOM_ELEMENT_HANDLING: r,
25
30
  ADD_TAGS: ["iframe"],
26
31
  ADD_ATTR: [
27
32
  "target",
@@ -40,10 +45,10 @@ var a = (e, t = {}) => {
40
45
  ]
41
46
  });
42
47
  } finally {
43
- i = null;
48
+ a = null;
44
49
  }
45
- }, o = (t) => e.sanitize(t);
50
+ }, s = (t) => e.sanitize(t, { CUSTOM_ELEMENT_HANDLING: r });
46
51
  //#endregion
47
- export { n as cleanHTML, a as sanitizeArticleHtml, o as sanitizeHtml, t as stripHtmlTags };
52
+ export { n as cleanHTML, o as sanitizeArticleHtml, s as sanitizeHtml, t as stripHtmlTags };
48
53
 
49
54
  //# sourceMappingURL=html.es.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"html.es.mjs","names":[],"sources":["../src/html/stripHtmlTags.ts","../src/html/cleanHTML.ts","../src/html/sanitizeArticleHtml.ts","../src/html/sanitizeHtml.ts"],"sourcesContent":["import DOMPurify from 'dompurify'\n\n/**\n * Strips all HTML tags, keeping the text content. From smart-search's\n * stripHtmlTags; its html-react-parser fallback is intentionally dropped to\n * avoid a heavy runtime dependency — the DOMPurify path plus a regex fallback is\n * sufficient and never throws in practice.\n * @param {string | undefined} html - String that may contain HTML tags.\n * @returns {string} The text content with tags removed.\n */\nexport const stripHtmlTags = (html?: string): string => {\n if (!html) return ''\n\n try {\n return DOMPurify.sanitize(html, { ALLOWED_TAGS: [], KEEP_CONTENT: true })\n } catch {\n return html.replace(/<\\/?[^>]+(>|$)/g, '')\n }\n}\n","import { stripHtmlTags } from './stripHtmlTags'\n\n/**\n * Normalizes HTML into a lowercase, punctuation-free, single-spaced token string\n * for search indexing/matching. From alerts' cleanHTML; builds on stripHtmlTags\n * so tag removal stays single-sourced.\n * @param {string} html - The HTML string to clean.\n * @returns {string} The cleaned, normalized text.\n */\nexport const cleanHTML = (html: string): string =>\n stripHtmlTags(html)\n .toLowerCase()\n .replace(/[.,!?;:\"'()[\\]\\-_/\\\\]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n","import createDOMPurify from 'dompurify'\n\nimport type { SanitizeArticleHtmlOptions } from '../types/html/SanitizeArticleHtmlOptions'\n\n// Dedicated DOMPurify instance so the hardening hook below is scoped to this\n// sanitizer and never pollutes the consumer's shared default DOMPurify instance\n// (other code may sanitize through the default instance directly).\nconst purifier = createDOMPurify(window)\n\n// Set synchronously around each (synchronous) sanitize call. JS is\n// single-threaded, so the guard cannot leak across calls.\nlet activeIframeGuard: ((src: string) => boolean) | null = null\n\n// afterSanitizeAttributes hook: force rel=\"noopener noreferrer\" on\n// target=\"_blank\" anchors (reverse-tabnabbing), and drop iframes whose src fails\n// the injected allowlist predicate when one is active.\npurifier.addHook('afterSanitizeAttributes', (node) => {\n if (!(node instanceof Element)) return\n\n if (node.tagName === 'A' && node.getAttribute('target') === '_blank') {\n node.setAttribute('rel', 'noopener noreferrer')\n }\n\n if (node.tagName === 'IFRAME' && activeIframeGuard) {\n const src = node.getAttribute('src') ?? ''\n if (!activeIframeGuard(src)) node.parentNode?.removeChild(node)\n }\n})\n\n/**\n * Canonical sanitizer for rich article HTML rendered into a session-bearing\n * webview. Superset of the alerts and unacknowledged-bulletins variants: keeps\n * iframes and data-* attributes (renderWidgets discovers embedded sub-widgets via\n * data-*), strips scripts/inline handlers/javascript:, and forces\n * rel=\"noopener noreferrer\" on target=\"_blank\" anchors. When options.isAllowedIframeSrc\n * is provided, iframes whose src fails it are dropped (injected so this module\n * does not depend on /links, which owns isAllowedIframeSrc).\n * @param {string} html - Raw article HTML from the Staffbase API.\n * @param {SanitizeArticleHtmlOptions} options - Optional iframe-src allowlist.\n * @returns {string} Sanitized HTML safe to inject/parse.\n */\nexport const sanitizeArticleHtml = (\n html: string,\n options: SanitizeArticleHtmlOptions = {},\n): string => {\n activeIframeGuard = options.isAllowedIframeSrc ?? null\n try {\n return purifier.sanitize(html, {\n USE_PROFILES: { html: true },\n ADD_TAGS: ['iframe'],\n ADD_ATTR: [\n 'target',\n 'allow',\n 'allowfullscreen',\n 'frameborder',\n 'scrolling',\n 'loading',\n 'referrerpolicy',\n ],\n FORBID_TAGS: ['script', 'style'],\n FORBID_ATTR: ['onerror', 'onload', 'onclick'],\n })\n } finally {\n activeIframeGuard = null\n }\n}\n","import DOMPurify from 'dompurify'\n\n/**\n * Strict sanitizer for untrusted snippet/teaser HTML rendered through a\n * non-sanitizing parser (e.g. html-react-parser). The default DOMPurify profile\n * strips scripts, inline handlers and dangerous URLs AND drops iframes, keeping\n * only basic rich-text markup. From global-content's sanitizeHtml.\n *\n * Use sanitizeArticleHtml instead when rendering full article bodies that must\n * keep iframes / data-* embeds.\n * @param {string} html - Raw HTML string from the API.\n * @returns {string} Sanitized HTML.\n */\nexport const sanitizeHtml = (html: string): string => DOMPurify.sanitize(html)\n"],"mappings":";;AAUA,IAAa,KAAiB,MAA0B;CACtD,IAAI,CAAC,GAAM,OAAO;CAElB,IAAI;EACF,OAAO,EAAU,SAAS,GAAM;GAAE,cAAc,CAAC;GAAG,cAAc;EAAK,CAAC;CAC1E,QAAQ;EACN,OAAO,EAAK,QAAQ,mBAAmB,EAAE;CAC3C;AACF,GCTa,KAAa,MACxB,EAAc,CAAI,EACf,YAAY,EACZ,QAAQ,0BAA0B,GAAG,EACrC,QAAQ,QAAQ,GAAG,EACnB,KAAK,GCPJ,IAAW,EAAgB,MAAM,GAInC,IAAuD;AAK3D,EAAS,QAAQ,4BAA4B,MAAS;CAC9C,iBAAgB,YAElB,EAAK,YAAY,OAAO,EAAK,aAAa,QAAQ,MAAM,YAC1D,EAAK,aAAa,OAAO,qBAAqB,GAG5C,EAAK,YAAY,YAAY,IAAmB;EAClD,IAAM,IAAM,EAAK,aAAa,KAAK,KAAK;EACxC,AAAK,EAAkB,CAAG,KAAG,EAAK,YAAY,YAAY,CAAI;CAChE;AACF,CAAC;AAcD,IAAa,KACX,GACA,IAAsC,CAAC,MAC5B;CACX,IAAoB,EAAQ,sBAAsB;CAClD,IAAI;EACF,OAAO,EAAS,SAAS,GAAM;GAC7B,cAAc,EAAE,MAAM,GAAK;GAC3B,UAAU,CAAC,QAAQ;GACnB,UAAU;IACR;IACA;IACA;IACA;IACA;IACA;IACA;GACF;GACA,aAAa,CAAC,UAAU,OAAO;GAC/B,aAAa;IAAC;IAAW;IAAU;GAAS;EAC9C,CAAC;CACH,UAAU;EACR,IAAoB;CACtB;AACF,GCpDa,KAAgB,MAAyB,EAAU,SAAS,CAAI"}
1
+ {"version":3,"file":"html.es.mjs","names":[],"sources":["../src/html/stripHtmlTags.ts","../src/html/cleanHTML.ts","../src/html/customElementHandling.ts","../src/html/sanitizeArticleHtml.ts","../src/html/sanitizeHtml.ts"],"sourcesContent":["import DOMPurify from 'dompurify'\n\n/**\n * Strips all HTML tags, keeping the text content. From smart-search's\n * stripHtmlTags; its html-react-parser fallback is intentionally dropped to\n * avoid a heavy runtime dependency — the DOMPurify path plus a regex fallback is\n * sufficient and never throws in practice.\n * @param {string | undefined} html - String that may contain HTML tags.\n * @returns {string} The text content with tags removed.\n */\nexport const stripHtmlTags = (html?: string): string => {\n if (!html) return ''\n\n try {\n return DOMPurify.sanitize(html, { ALLOWED_TAGS: [], KEEP_CONTENT: true })\n } catch {\n return html.replace(/<\\/?[^>]+(>|$)/g, '')\n }\n}\n","import { stripHtmlTags } from './stripHtmlTags'\n\n/**\n * Normalizes HTML into a lowercase, punctuation-free, single-spaced token string\n * for search indexing/matching. From alerts' cleanHTML; builds on stripHtmlTags\n * so tag removal stays single-sourced.\n * @param {string} html - The HTML string to clean.\n * @returns {string} The cleaned, normalized text.\n */\nexport const cleanHTML = (html: string): string =>\n stripHtmlTags(html)\n .toLowerCase()\n .replace(/[.,!?;:\"'()[\\]\\-_/\\\\]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n","import type { Config } from 'dompurify'\n\n/**\n * DOMPurify custom-element handling that preserves embedded-widget custom\n * elements (any hyphenated tag, e.g. `<news-teaser>`) and their kebab-case /\n * `data-` attributes so the host widget manager (renderWidgets) can hydrate\n * them. Without this, DOMPurify's default profile silently strips unknown custom\n * elements AND their attributes, wiping embeds out of the content before they can\n * render. `on*` event-handler attributes are still rejected (defense in depth\n * alongside DOMPurify's own XSS stripping). Promoted from staffbase-global-content.\n */\nexport const customElementHandling: NonNullable<\n Config['CUSTOM_ELEMENT_HANDLING']\n> = {\n // Any valid custom-element tag name (must contain a hyphen per the spec).\n tagNameCheck: /^[a-z][a-z0-9]*-[a-z0-9-]*$/,\n // Allow kebab-case / data- attributes on custom elements, but never `on*`.\n attributeNameCheck: /^(?!on)[a-z][a-z0-9]*([-:][a-z0-9]+)*$/,\n // Permit `<div is=\"some-widget\">`-style customized built-in elements.\n allowCustomizedBuiltInElements: true,\n}\n","import createDOMPurify from 'dompurify'\n\nimport type { SanitizeArticleHtmlOptions } from '../types/html/SanitizeArticleHtmlOptions'\nimport { customElementHandling } from './customElementHandling'\n\n// Dedicated DOMPurify instance so the hardening hook below is scoped to this\n// sanitizer and never pollutes the consumer's shared default DOMPurify instance\n// (other code may sanitize through the default instance directly).\nconst purifier = createDOMPurify(window)\n\n// Set synchronously around each (synchronous) sanitize call. JS is\n// single-threaded, so the guard cannot leak across calls.\nlet activeIframeGuard: ((src: string) => boolean) | null = null\n\n// afterSanitizeAttributes hook: force rel=\"noopener noreferrer\" on\n// target=\"_blank\" anchors (reverse-tabnabbing), and drop iframes whose src fails\n// the injected allowlist predicate when one is active.\npurifier.addHook('afterSanitizeAttributes', (node) => {\n if (!(node instanceof Element)) return\n\n if (node.tagName === 'A' && node.getAttribute('target') === '_blank') {\n node.setAttribute('rel', 'noopener noreferrer')\n }\n\n if (node.tagName === 'IFRAME' && activeIframeGuard) {\n const src = node.getAttribute('src') ?? ''\n if (!activeIframeGuard(src)) node.parentNode?.removeChild(node)\n }\n})\n\n/**\n * Canonical sanitizer for rich article HTML rendered into a session-bearing\n * webview. Superset of the alerts and unacknowledged-bulletins variants: keeps\n * iframes and data-* attributes (renderWidgets discovers embedded sub-widgets via\n * data-*), strips scripts/inline handlers/javascript:, and forces\n * rel=\"noopener noreferrer\" on target=\"_blank\" anchors. When options.isAllowedIframeSrc\n * is provided, iframes whose src fails it are dropped (injected so this module\n * does not depend on /links, which owns isAllowedIframeSrc).\n * @param {string} html - Raw article HTML from the Staffbase API.\n * @param {SanitizeArticleHtmlOptions} options - Optional iframe-src allowlist.\n * @returns {string} Sanitized HTML safe to inject/parse.\n */\nexport const sanitizeArticleHtml = (\n html: string,\n options: SanitizeArticleHtmlOptions = {},\n): string => {\n activeIframeGuard = options.isAllowedIframeSrc ?? null\n try {\n return purifier.sanitize(html, {\n USE_PROFILES: { html: true },\n CUSTOM_ELEMENT_HANDLING: customElementHandling,\n ADD_TAGS: ['iframe'],\n ADD_ATTR: [\n 'target',\n 'allow',\n 'allowfullscreen',\n 'frameborder',\n 'scrolling',\n 'loading',\n 'referrerpolicy',\n ],\n FORBID_TAGS: ['script', 'style'],\n FORBID_ATTR: ['onerror', 'onload', 'onclick'],\n })\n } finally {\n activeIframeGuard = null\n }\n}\n","import DOMPurify from 'dompurify'\n\nimport { customElementHandling } from './customElementHandling'\n\n/**\n * Strict sanitizer for untrusted snippet/teaser HTML rendered through a\n * non-sanitizing parser (e.g. html-react-parser). The default DOMPurify profile\n * strips scripts, inline handlers and dangerous URLs AND drops iframes, keeping\n * only basic rich-text markup. Embedded-widget custom elements (and their\n * kebab/data attributes) are preserved so the host can hydrate them. From\n * global-content's sanitizeHtml.\n *\n * Use sanitizeArticleHtml instead when rendering full article bodies that must\n * keep iframes / data-* embeds.\n * @param {string} html - Raw HTML string from the API.\n * @returns {string} Sanitized HTML.\n */\nexport const sanitizeHtml = (html: string): string =>\n DOMPurify.sanitize(html, { CUSTOM_ELEMENT_HANDLING: customElementHandling })\n"],"mappings":";;AAUA,IAAa,KAAiB,MAA0B;CACtD,IAAI,CAAC,GAAM,OAAO;CAElB,IAAI;EACF,OAAO,EAAU,SAAS,GAAM;GAAE,cAAc,CAAC;GAAG,cAAc;EAAK,CAAC;CAC1E,QAAQ;EACN,OAAO,EAAK,QAAQ,mBAAmB,EAAE;CAC3C;AACF,GCTa,KAAa,MACxB,EAAc,CAAI,EACf,YAAY,EACZ,QAAQ,0BAA0B,GAAG,EACrC,QAAQ,QAAQ,GAAG,EACnB,KAAK,GCHG,IAET;CAEF,cAAc;CAEd,oBAAoB;CAEpB,gCAAgC;AAClC,GCZM,IAAW,EAAgB,MAAM,GAInC,IAAuD;AAK3D,EAAS,QAAQ,4BAA4B,MAAS;CAC9C,iBAAgB,YAElB,EAAK,YAAY,OAAO,EAAK,aAAa,QAAQ,MAAM,YAC1D,EAAK,aAAa,OAAO,qBAAqB,GAG5C,EAAK,YAAY,YAAY,IAAmB;EAClD,IAAM,IAAM,EAAK,aAAa,KAAK,KAAK;EACxC,AAAK,EAAkB,CAAG,KAAG,EAAK,YAAY,YAAY,CAAI;CAChE;AACF,CAAC;AAcD,IAAa,KACX,GACA,IAAsC,CAAC,MAC5B;CACX,IAAoB,EAAQ,sBAAsB;CAClD,IAAI;EACF,OAAO,EAAS,SAAS,GAAM;GAC7B,cAAc,EAAE,MAAM,GAAK;GAC3B,yBAAyB;GACzB,UAAU,CAAC,QAAQ;GACnB,UAAU;IACR;IACA;IACA;IACA;IACA;IACA;IACA;GACF;GACA,aAAa,CAAC,UAAU,OAAO;GAC/B,aAAa;IAAC;IAAW;IAAU;GAAS;EAC9C,CAAC;CACH,UAAU;EACR,IAAoB;CACtB;AACF,GClDa,KAAgB,MAC3B,EAAU,SAAS,GAAM,EAAE,yBAAyB,EAAsB,CAAC"}
@@ -0,0 +1,81 @@
1
+ //#region src/widgets/defaultWidgetOnError.ts
2
+ var e = (e, t) => {
3
+ t === "manager-unavailable" && console.warn("[staffbase-utils] Staffbase widget manager unavailable; embedded widgets were not rendered.", e);
4
+ }, t = () => {
5
+ if (typeof window > "u") return null;
6
+ let e = window.staffbase?.content?.widgetMgr;
7
+ return typeof e == "function" ? e : null;
8
+ }, n = () => typeof window > "u" ? null : window.staffbase?.content?.widgetMgr?.prototype ?? null, r = (e) => typeof e._extractWidgets == "function" && typeof e._renderWidget == "function", i = (e) => e instanceof TypeError && (e.message?.includes("each") || e.message?.includes("undefined is not an object")), a = Promise.resolve(), o = /* @__PURE__ */ new WeakMap(), s = !1, c = async (e, a, c, l, u, d) => {
9
+ let f = 0, p = !1;
10
+ for (; f < l;) {
11
+ if (f++, o.get(a) !== c) return {
12
+ ok: !1,
13
+ reason: "cancelled"
14
+ };
15
+ if (typeof e.querySelectorAll != "function") return {
16
+ ok: !1,
17
+ reason: "no-container"
18
+ };
19
+ let s = t();
20
+ if (s) {
21
+ p = !0;
22
+ try {
23
+ let t = new s(void 0, !1);
24
+ if (typeof t.render == "function") return await t.render(e), {
25
+ ok: !0,
26
+ rendered: 0
27
+ };
28
+ } catch {}
29
+ }
30
+ let m = n();
31
+ if (m && r(m)) {
32
+ p = !0, Array.isArray(m._widgets) || (m._widgets = []);
33
+ let t;
34
+ try {
35
+ t = m._extractWidgets(e);
36
+ } catch (e) {
37
+ d(e, "extract"), t = [];
38
+ }
39
+ if (t.length > 0) {
40
+ let n = 0;
41
+ for (let r of t) {
42
+ if (o.get(a) !== c) return {
43
+ ok: !1,
44
+ reason: "cancelled"
45
+ };
46
+ try {
47
+ m._renderWidget.call(m, e, r), n++;
48
+ } catch (e) {
49
+ i(e) || d(e, "render-widget");
50
+ }
51
+ }
52
+ return {
53
+ ok: !0,
54
+ rendered: n
55
+ };
56
+ }
57
+ }
58
+ if (f >= l) break;
59
+ await new Promise((e) => setTimeout(e, u));
60
+ }
61
+ return p ? {
62
+ ok: !1,
63
+ reason: "no-widgets"
64
+ } : (s || (s = !0, d(/* @__PURE__ */ Error("Staffbase widget manager unavailable after retries."), "manager-unavailable")), {
65
+ ok: !1,
66
+ reason: "manager-unavailable"
67
+ });
68
+ }, l = (t, n = {}) => {
69
+ if (!t) return Promise.resolve({
70
+ ok: !1,
71
+ reason: "no-container"
72
+ });
73
+ let { maxRetries: r = 10, retryDelay: i = 300, onError: s = e, cancelKey: l = t } = n, u = (o.get(l) ?? 0) + 1;
74
+ o.set(l, u);
75
+ let d = a.then(() => c(t, l, u, r, i, s));
76
+ return a = d.then(() => void 0, () => void 0), d;
77
+ };
78
+ //#endregion
79
+ export { l as t };
80
+
81
+ //# sourceMappingURL=renderWidgets-CeIczubt.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderWidgets-CeIczubt.mjs","names":[],"sources":["../src/widgets/defaultWidgetOnError.ts","../src/widgets/getWidgetManagerConstructor.ts","../src/widgets/getWidgetManagerPrototype.ts","../src/widgets/hasRequiredWidgetManagerMethods.ts","../src/widgets/isKnownStaffbaseRenderError.ts","../src/widgets/renderWidgets.ts"],"sourcesContent":["/**\n * Default onError for renderWidgets: warns only on the critical\n * 'manager-unavailable' context so a platform regression stays visible in logs\n * without spamming per-widget noise. Consumers can pass their own onError.\n * @param {unknown} error - The swallowed error.\n * @param {string} context - Where it happened.\n * @returns {void}\n */\nexport const defaultWidgetOnError = (error: unknown, context: string): void => {\n if (context === 'manager-unavailable') {\n console.warn(\n '[staffbase-utils] Staffbase widget manager unavailable; embedded widgets were not rendered.',\n error,\n )\n }\n}\n","import type { StaffbaseWidgetManagerConstructor } from '../types/widgets/StaffbaseWidgetManagerConstructor'\n\n/**\n * Resolves the host widget-manager constructor from the global Staffbase object,\n * or null when it is not a function (older runtimes / platform regression).\n * @returns {StaffbaseWidgetManagerConstructor | null} The constructor or null.\n */\nexport const getWidgetManagerConstructor =\n (): StaffbaseWidgetManagerConstructor | null => {\n if (typeof window === 'undefined') return null\n const ctor = (\n window as unknown as {\n staffbase?: { content?: { widgetMgr?: unknown } }\n }\n ).staffbase?.content?.widgetMgr\n return typeof ctor === 'function'\n ? (ctor as StaffbaseWidgetManagerConstructor)\n : null\n }\n","import type { StaffbaseWidgetManagerPrototype } from '../types/widgets/StaffbaseWidgetManagerPrototype'\n\n/**\n * Resolves the host widget-manager prototype (the private fallback API) from the\n * global Staffbase object, or null when it is unavailable.\n * @returns {StaffbaseWidgetManagerPrototype | null} The prototype or null.\n */\nexport const getWidgetManagerPrototype =\n (): StaffbaseWidgetManagerPrototype | null => {\n if (typeof window === 'undefined') return null\n const proto = (\n window as unknown as {\n staffbase?: {\n content?: {\n widgetMgr?: { prototype?: StaffbaseWidgetManagerPrototype }\n }\n }\n }\n ).staffbase?.content?.widgetMgr?.prototype\n return proto ?? null\n }\n","import type { StaffbaseWidgetManagerPrototype } from '../types/widgets/StaffbaseWidgetManagerPrototype'\n\n/**\n * Type guard asserting the widget-manager prototype exposes the methods the\n * prototype render path needs.\n * @param {StaffbaseWidgetManagerPrototype} widgetMgr - The resolved prototype.\n * @returns {boolean} True when both _extractWidgets and _renderWidget are functions.\n */\nexport const hasRequiredWidgetManagerMethods = (\n widgetMgr: StaffbaseWidgetManagerPrototype,\n): widgetMgr is StaffbaseWidgetManagerPrototype & {\n _extractWidgets: (container: HTMLElement) => unknown[]\n _renderWidget: (\n this: StaffbaseWidgetManagerPrototype,\n container: HTMLElement,\n widget: unknown,\n ) => void\n} =>\n typeof widgetMgr._extractWidgets === 'function' &&\n typeof widgetMgr._renderWidget === 'function'\n","/**\n * Detects known, benign internal Staffbase errors thrown by `_renderWidget` for\n * individual widgets, so they can be swallowed without aborting the batch.\n * Promoted from unacknowledged-bulletins (`each` / `undefined is not an object`).\n * @param {unknown} error - The thrown value.\n * @returns {boolean} True when the error is a known internal render error.\n */\nexport const isKnownStaffbaseRenderError = (error: unknown): boolean =>\n error instanceof TypeError &&\n (error.message?.includes('each') ||\n error.message?.includes('undefined is not an object'))\n","import type { RenderWidgetsOptions } from '../types/widgets/RenderWidgetsOptions'\nimport type { RenderWidgetsResult } from '../types/widgets/RenderWidgetsResult'\n\nimport { defaultWidgetOnError } from './defaultWidgetOnError'\nimport { getWidgetManagerConstructor } from './getWidgetManagerConstructor'\nimport { getWidgetManagerPrototype } from './getWidgetManagerPrototype'\nimport { hasRequiredWidgetManagerMethods } from './hasRequiredWidgetManagerMethods'\nimport { isKnownStaffbaseRenderError } from './isKnownStaffbaseRenderError'\n\n// Global serialization chain: all renders run one at a time so they never race\n// on the host's shared `_widgets` array (unacknowledged-bulletins' lock, made\n// queue-based). Per-cancelKey run ids drop superseded renders (alerts' WeakMap\n// cancellation; no AbortController, for old webviews). warn-once keeps a missing\n// manager observable without spam (alerts).\nlet queue: Promise<unknown> = Promise.resolve()\nconst cancelTokens = new WeakMap<object, number>()\nlet hasWarnedManagerUnavailable = false\n\n/**\n * Runs the retry loop for a single render: constructor path first, prototype\n * fallback, with cancellation checks and known-error swallowing.\n * @param {HTMLElement} container - The container to render widgets into.\n * @param {object} cancelKey - The cancellation key for this render.\n * @param {number} runId - This render's run id for the cancel key.\n * @param {number} maxRetries - Maximum render attempts.\n * @param {number} retryDelay - Delay between attempts, in milliseconds.\n * @param {(error: unknown, context: string) => void} onError - Error reporter.\n * @returns {Promise<RenderWidgetsResult>} The typed render result.\n */\nconst executeRender = async (\n container: HTMLElement,\n cancelKey: object,\n runId: number,\n maxRetries: number,\n retryDelay: number,\n onError: (error: unknown, context: string) => void,\n): Promise<RenderWidgetsResult> => {\n let attempts = 0\n let managerSeen = false\n\n while (attempts < maxRetries) {\n attempts++\n if (cancelTokens.get(cancelKey) !== runId)\n return { ok: false, reason: 'cancelled' }\n if (typeof container.querySelectorAll !== 'function') {\n return { ok: false, reason: 'no-container' }\n }\n\n // Prefer Staffbase's real widget manager (closest to host behavior).\n const ctor = getWidgetManagerConstructor()\n if (ctor) {\n managerSeen = true\n try {\n const instance = new ctor(undefined, false)\n if (typeof instance.render === 'function') {\n await instance.render(container)\n return { ok: true, rendered: 0 }\n }\n } catch {\n // Fall back to the private prototype path below.\n }\n }\n\n const proto = getWidgetManagerPrototype()\n if (proto && hasRequiredWidgetManagerMethods(proto)) {\n managerSeen = true\n if (!Array.isArray(proto._widgets)) proto._widgets = []\n\n let widgets: unknown[]\n try {\n widgets = proto._extractWidgets(container)\n } catch (error) {\n onError(error, 'extract')\n widgets = []\n }\n\n if (widgets.length > 0) {\n let rendered = 0\n for (const widget of widgets) {\n if (cancelTokens.get(cancelKey) !== runId) {\n return { ok: false, reason: 'cancelled' }\n }\n try {\n proto._renderWidget.call(proto, container, widget)\n rendered++\n } catch (error) {\n if (!isKnownStaffbaseRenderError(error))\n onError(error, 'render-widget')\n }\n }\n return { ok: true, rendered }\n }\n }\n\n if (attempts >= maxRetries) break\n await new Promise((resolve) => setTimeout(resolve, retryDelay))\n }\n\n if (!managerSeen) {\n if (!hasWarnedManagerUnavailable) {\n hasWarnedManagerUnavailable = true\n onError(\n new Error('Staffbase widget manager unavailable after retries.'),\n 'manager-unavailable',\n )\n }\n return { ok: false, reason: 'manager-unavailable' }\n }\n return { ok: false, reason: 'no-widgets' }\n}\n\n/**\n * Renders the widgets embedded in `container` using the host's private widget\n * manager. Superset of the four widget services: constructor path first with a\n * prototype fallback, a global queue that serializes renders, per-cancelKey\n * cancellation of superseded renders, configurable retries, swallowing of known\n * internal Staffbase errors, and a once-per-session warning when the manager is\n * missing. Returns a typed result so callers can react (e.g. mark a\n * data-widget-render-error attribute).\n * @param {HTMLElement | null} container - The element whose embedded widgets are rendered.\n * @param {RenderWidgetsOptions} options - Retry, error and cancellation options.\n * @returns {Promise<RenderWidgetsResult>} The typed render result.\n */\nexport const renderWidgets = (\n container: HTMLElement | null,\n options: RenderWidgetsOptions = {},\n): Promise<RenderWidgetsResult> => {\n if (!container) return Promise.resolve({ ok: false, reason: 'no-container' })\n\n const {\n maxRetries = 10,\n retryDelay = 300,\n onError = defaultWidgetOnError,\n cancelKey = container,\n } = options\n\n // Bump the run id synchronously so any older in-flight/queued render for the\n // same key sees itself as superseded.\n const runId = (cancelTokens.get(cancelKey) ?? 0) + 1\n cancelTokens.set(cancelKey, runId)\n\n const run = queue.then(() =>\n executeRender(container, cancelKey, runId, maxRetries, retryDelay, onError),\n )\n // Keep the queue chain alive regardless of individual outcomes.\n queue = run.then(\n () => undefined,\n () => undefined,\n )\n return run\n}\n"],"mappings":";AAQA,IAAa,KAAwB,GAAgB,MAA0B;CAC7E,AAAI,MAAY,yBACd,QAAQ,KACN,+FACA,CACF;AAEJ,GCRa,UACqC;CAC9C,IAAI,OAAO,SAAW,KAAa,OAAO;CAC1C,IAAM,IACJ,OAGA,WAAW,SAAS;CACtB,OAAO,OAAO,KAAS,aAClB,IACD;AACN,GCXW,UAEL,OAAO,SAAW,MAAoB,OAExC,OAOA,WAAW,SAAS,WAAW,aACjB,MCXP,KACX,MASA,OAAO,EAAU,mBAAoB,cACrC,OAAO,EAAU,iBAAkB,YCZxB,KAA+B,MAC1C,aAAiB,cAChB,EAAM,SAAS,SAAS,MAAM,KAC7B,EAAM,SAAS,SAAS,4BAA4B,ICIpD,IAA0B,QAAQ,QAAQ,GACxC,oBAAe,IAAI,QAAwB,GAC7C,IAA8B,IAa5B,IAAgB,OACpB,GACA,GACA,GACA,GACA,GACA,MACiC;CACjC,IAAI,IAAW,GACX,IAAc;CAElB,OAAO,IAAW,IAAY;EAE5B,IADA,KACI,EAAa,IAAI,CAAS,MAAM,GAClC,OAAO;GAAE,IAAI;GAAO,QAAQ;EAAY;EAC1C,IAAI,OAAO,EAAU,oBAAqB,YACxC,OAAO;GAAE,IAAI;GAAO,QAAQ;EAAe;EAI7C,IAAM,IAAO,EAA4B;EACzC,IAAI,GAAM;GACR,IAAc;GACd,IAAI;IACF,IAAM,IAAW,IAAI,EAAK,KAAA,GAAW,EAAK;IAC1C,IAAI,OAAO,EAAS,UAAW,YAE7B,OADA,MAAM,EAAS,OAAO,CAAS,GACxB;KAAE,IAAI;KAAM,UAAU;IAAE;GAEnC,QAAQ,CAER;EACF;EAEA,IAAM,IAAQ,EAA0B;EACxC,IAAI,KAAS,EAAgC,CAAK,GAAG;GAEnD,AADA,IAAc,IACT,MAAM,QAAQ,EAAM,QAAQ,MAAG,EAAM,WAAW,CAAC;GAEtD,IAAI;GACJ,IAAI;IACF,IAAU,EAAM,gBAAgB,CAAS;GAC3C,SAAS,GAAO;IAEd,AADA,EAAQ,GAAO,SAAS,GACxB,IAAU,CAAC;GACb;GAEA,IAAI,EAAQ,SAAS,GAAG;IACtB,IAAI,IAAW;IACf,KAAK,IAAM,KAAU,GAAS;KAC5B,IAAI,EAAa,IAAI,CAAS,MAAM,GAClC,OAAO;MAAE,IAAI;MAAO,QAAQ;KAAY;KAE1C,IAAI;MAEF,AADA,EAAM,cAAc,KAAK,GAAO,GAAW,CAAM,GACjD;KACF,SAAS,GAAO;MACd,AAAK,EAA4B,CAAK,KACpC,EAAQ,GAAO,eAAe;KAClC;IACF;IACA,OAAO;KAAE,IAAI;KAAM;IAAS;GAC9B;EACF;EAEA,IAAI,KAAY,GAAY;EAC5B,MAAM,IAAI,SAAS,MAAY,WAAW,GAAS,CAAU,CAAC;CAChE;CAYA,OAVK,IAUE;EAAE,IAAI;EAAO,QAAQ;CAAa,KATlC,MACH,IAA8B,IAC9B,EACE,gBAAI,MAAM,qDAAqD,GAC/D,qBACF,IAEK;EAAE,IAAI;EAAO,QAAQ;CAAsB;AAGtD,GAca,KACX,GACA,IAAgC,CAAC,MACA;CACjC,IAAI,CAAC,GAAW,OAAO,QAAQ,QAAQ;EAAE,IAAI;EAAO,QAAQ;CAAe,CAAC;CAE5E,IAAM,EACJ,gBAAa,IACb,gBAAa,KACb,aAAU,GACV,eAAY,MACV,GAIE,KAAS,EAAa,IAAI,CAAS,KAAK,KAAK;CACnD,EAAa,IAAI,GAAW,CAAK;CAEjC,IAAM,IAAM,EAAM,WAChB,EAAc,GAAW,GAAW,GAAO,GAAY,GAAY,CAAO,CAC5E;CAMA,OAJA,IAAQ,EAAI,WACJ,KAAA,SACA,KAAA,CACR,GACO;AACT"}
@@ -0,0 +1,2 @@
1
+ var e=(e,t)=>{t===`manager-unavailable`&&console.warn(`[staffbase-utils] Staffbase widget manager unavailable; embedded widgets were not rendered.`,e)},t=()=>{if(typeof window>`u`)return null;let e=window.staffbase?.content?.widgetMgr;return typeof e==`function`?e:null},n=()=>typeof window>`u`?null:window.staffbase?.content?.widgetMgr?.prototype??null,r=e=>typeof e._extractWidgets==`function`&&typeof e._renderWidget==`function`,i=e=>e instanceof TypeError&&(e.message?.includes(`each`)||e.message?.includes(`undefined is not an object`)),a=Promise.resolve(),o=new WeakMap,s=!1,c=async(e,a,c,l,u,d)=>{let f=0,p=!1;for(;f<l;){if(f++,o.get(a)!==c)return{ok:!1,reason:`cancelled`};if(typeof e.querySelectorAll!=`function`)return{ok:!1,reason:`no-container`};let s=t();if(s){p=!0;try{let t=new s(void 0,!1);if(typeof t.render==`function`)return await t.render(e),{ok:!0,rendered:0}}catch{}}let m=n();if(m&&r(m)){p=!0,Array.isArray(m._widgets)||(m._widgets=[]);let t;try{t=m._extractWidgets(e)}catch(e){d(e,`extract`),t=[]}if(t.length>0){let n=0;for(let r of t){if(o.get(a)!==c)return{ok:!1,reason:`cancelled`};try{m._renderWidget.call(m,e,r),n++}catch(e){i(e)||d(e,`render-widget`)}}return{ok:!0,rendered:n}}}if(f>=l)break;await new Promise(e=>setTimeout(e,u))}return p?{ok:!1,reason:`no-widgets`}:(s||(s=!0,d(Error(`Staffbase widget manager unavailable after retries.`),`manager-unavailable`)),{ok:!1,reason:`manager-unavailable`})},l=(t,n={})=>{if(!t)return Promise.resolve({ok:!1,reason:`no-container`});let{maxRetries:r=10,retryDelay:i=300,onError:s=e,cancelKey:l=t}=n,u=(o.get(l)??0)+1;o.set(l,u);let d=a.then(()=>c(t,l,u,r,i,s));return a=d.then(()=>void 0,()=>void 0),d};Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return l}});
2
+ //# sourceMappingURL=renderWidgets-EbJq9Wn6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderWidgets-EbJq9Wn6.js","names":[],"sources":["../src/widgets/defaultWidgetOnError.ts","../src/widgets/getWidgetManagerConstructor.ts","../src/widgets/getWidgetManagerPrototype.ts","../src/widgets/hasRequiredWidgetManagerMethods.ts","../src/widgets/isKnownStaffbaseRenderError.ts","../src/widgets/renderWidgets.ts"],"sourcesContent":["/**\n * Default onError for renderWidgets: warns only on the critical\n * 'manager-unavailable' context so a platform regression stays visible in logs\n * without spamming per-widget noise. Consumers can pass their own onError.\n * @param {unknown} error - The swallowed error.\n * @param {string} context - Where it happened.\n * @returns {void}\n */\nexport const defaultWidgetOnError = (error: unknown, context: string): void => {\n if (context === 'manager-unavailable') {\n console.warn(\n '[staffbase-utils] Staffbase widget manager unavailable; embedded widgets were not rendered.',\n error,\n )\n }\n}\n","import type { StaffbaseWidgetManagerConstructor } from '../types/widgets/StaffbaseWidgetManagerConstructor'\n\n/**\n * Resolves the host widget-manager constructor from the global Staffbase object,\n * or null when it is not a function (older runtimes / platform regression).\n * @returns {StaffbaseWidgetManagerConstructor | null} The constructor or null.\n */\nexport const getWidgetManagerConstructor =\n (): StaffbaseWidgetManagerConstructor | null => {\n if (typeof window === 'undefined') return null\n const ctor = (\n window as unknown as {\n staffbase?: { content?: { widgetMgr?: unknown } }\n }\n ).staffbase?.content?.widgetMgr\n return typeof ctor === 'function'\n ? (ctor as StaffbaseWidgetManagerConstructor)\n : null\n }\n","import type { StaffbaseWidgetManagerPrototype } from '../types/widgets/StaffbaseWidgetManagerPrototype'\n\n/**\n * Resolves the host widget-manager prototype (the private fallback API) from the\n * global Staffbase object, or null when it is unavailable.\n * @returns {StaffbaseWidgetManagerPrototype | null} The prototype or null.\n */\nexport const getWidgetManagerPrototype =\n (): StaffbaseWidgetManagerPrototype | null => {\n if (typeof window === 'undefined') return null\n const proto = (\n window as unknown as {\n staffbase?: {\n content?: {\n widgetMgr?: { prototype?: StaffbaseWidgetManagerPrototype }\n }\n }\n }\n ).staffbase?.content?.widgetMgr?.prototype\n return proto ?? null\n }\n","import type { StaffbaseWidgetManagerPrototype } from '../types/widgets/StaffbaseWidgetManagerPrototype'\n\n/**\n * Type guard asserting the widget-manager prototype exposes the methods the\n * prototype render path needs.\n * @param {StaffbaseWidgetManagerPrototype} widgetMgr - The resolved prototype.\n * @returns {boolean} True when both _extractWidgets and _renderWidget are functions.\n */\nexport const hasRequiredWidgetManagerMethods = (\n widgetMgr: StaffbaseWidgetManagerPrototype,\n): widgetMgr is StaffbaseWidgetManagerPrototype & {\n _extractWidgets: (container: HTMLElement) => unknown[]\n _renderWidget: (\n this: StaffbaseWidgetManagerPrototype,\n container: HTMLElement,\n widget: unknown,\n ) => void\n} =>\n typeof widgetMgr._extractWidgets === 'function' &&\n typeof widgetMgr._renderWidget === 'function'\n","/**\n * Detects known, benign internal Staffbase errors thrown by `_renderWidget` for\n * individual widgets, so they can be swallowed without aborting the batch.\n * Promoted from unacknowledged-bulletins (`each` / `undefined is not an object`).\n * @param {unknown} error - The thrown value.\n * @returns {boolean} True when the error is a known internal render error.\n */\nexport const isKnownStaffbaseRenderError = (error: unknown): boolean =>\n error instanceof TypeError &&\n (error.message?.includes('each') ||\n error.message?.includes('undefined is not an object'))\n","import type { RenderWidgetsOptions } from '../types/widgets/RenderWidgetsOptions'\nimport type { RenderWidgetsResult } from '../types/widgets/RenderWidgetsResult'\n\nimport { defaultWidgetOnError } from './defaultWidgetOnError'\nimport { getWidgetManagerConstructor } from './getWidgetManagerConstructor'\nimport { getWidgetManagerPrototype } from './getWidgetManagerPrototype'\nimport { hasRequiredWidgetManagerMethods } from './hasRequiredWidgetManagerMethods'\nimport { isKnownStaffbaseRenderError } from './isKnownStaffbaseRenderError'\n\n// Global serialization chain: all renders run one at a time so they never race\n// on the host's shared `_widgets` array (unacknowledged-bulletins' lock, made\n// queue-based). Per-cancelKey run ids drop superseded renders (alerts' WeakMap\n// cancellation; no AbortController, for old webviews). warn-once keeps a missing\n// manager observable without spam (alerts).\nlet queue: Promise<unknown> = Promise.resolve()\nconst cancelTokens = new WeakMap<object, number>()\nlet hasWarnedManagerUnavailable = false\n\n/**\n * Runs the retry loop for a single render: constructor path first, prototype\n * fallback, with cancellation checks and known-error swallowing.\n * @param {HTMLElement} container - The container to render widgets into.\n * @param {object} cancelKey - The cancellation key for this render.\n * @param {number} runId - This render's run id for the cancel key.\n * @param {number} maxRetries - Maximum render attempts.\n * @param {number} retryDelay - Delay between attempts, in milliseconds.\n * @param {(error: unknown, context: string) => void} onError - Error reporter.\n * @returns {Promise<RenderWidgetsResult>} The typed render result.\n */\nconst executeRender = async (\n container: HTMLElement,\n cancelKey: object,\n runId: number,\n maxRetries: number,\n retryDelay: number,\n onError: (error: unknown, context: string) => void,\n): Promise<RenderWidgetsResult> => {\n let attempts = 0\n let managerSeen = false\n\n while (attempts < maxRetries) {\n attempts++\n if (cancelTokens.get(cancelKey) !== runId)\n return { ok: false, reason: 'cancelled' }\n if (typeof container.querySelectorAll !== 'function') {\n return { ok: false, reason: 'no-container' }\n }\n\n // Prefer Staffbase's real widget manager (closest to host behavior).\n const ctor = getWidgetManagerConstructor()\n if (ctor) {\n managerSeen = true\n try {\n const instance = new ctor(undefined, false)\n if (typeof instance.render === 'function') {\n await instance.render(container)\n return { ok: true, rendered: 0 }\n }\n } catch {\n // Fall back to the private prototype path below.\n }\n }\n\n const proto = getWidgetManagerPrototype()\n if (proto && hasRequiredWidgetManagerMethods(proto)) {\n managerSeen = true\n if (!Array.isArray(proto._widgets)) proto._widgets = []\n\n let widgets: unknown[]\n try {\n widgets = proto._extractWidgets(container)\n } catch (error) {\n onError(error, 'extract')\n widgets = []\n }\n\n if (widgets.length > 0) {\n let rendered = 0\n for (const widget of widgets) {\n if (cancelTokens.get(cancelKey) !== runId) {\n return { ok: false, reason: 'cancelled' }\n }\n try {\n proto._renderWidget.call(proto, container, widget)\n rendered++\n } catch (error) {\n if (!isKnownStaffbaseRenderError(error))\n onError(error, 'render-widget')\n }\n }\n return { ok: true, rendered }\n }\n }\n\n if (attempts >= maxRetries) break\n await new Promise((resolve) => setTimeout(resolve, retryDelay))\n }\n\n if (!managerSeen) {\n if (!hasWarnedManagerUnavailable) {\n hasWarnedManagerUnavailable = true\n onError(\n new Error('Staffbase widget manager unavailable after retries.'),\n 'manager-unavailable',\n )\n }\n return { ok: false, reason: 'manager-unavailable' }\n }\n return { ok: false, reason: 'no-widgets' }\n}\n\n/**\n * Renders the widgets embedded in `container` using the host's private widget\n * manager. Superset of the four widget services: constructor path first with a\n * prototype fallback, a global queue that serializes renders, per-cancelKey\n * cancellation of superseded renders, configurable retries, swallowing of known\n * internal Staffbase errors, and a once-per-session warning when the manager is\n * missing. Returns a typed result so callers can react (e.g. mark a\n * data-widget-render-error attribute).\n * @param {HTMLElement | null} container - The element whose embedded widgets are rendered.\n * @param {RenderWidgetsOptions} options - Retry, error and cancellation options.\n * @returns {Promise<RenderWidgetsResult>} The typed render result.\n */\nexport const renderWidgets = (\n container: HTMLElement | null,\n options: RenderWidgetsOptions = {},\n): Promise<RenderWidgetsResult> => {\n if (!container) return Promise.resolve({ ok: false, reason: 'no-container' })\n\n const {\n maxRetries = 10,\n retryDelay = 300,\n onError = defaultWidgetOnError,\n cancelKey = container,\n } = options\n\n // Bump the run id synchronously so any older in-flight/queued render for the\n // same key sees itself as superseded.\n const runId = (cancelTokens.get(cancelKey) ?? 0) + 1\n cancelTokens.set(cancelKey, runId)\n\n const run = queue.then(() =>\n executeRender(container, cancelKey, runId, maxRetries, retryDelay, onError),\n )\n // Keep the queue chain alive regardless of individual outcomes.\n queue = run.then(\n () => undefined,\n () => undefined,\n )\n return run\n}\n"],"mappings":"AAQA,IAAa,GAAwB,EAAgB,IAA0B,CACzE,IAAY,uBACd,QAAQ,KACN,8FACA,CACF,CAEJ,ECRa,MACqC,CAC9C,GAAI,OAAO,OAAW,IAAa,OAAO,KAC1C,IAAM,EACJ,OAGA,WAAW,SAAS,UACtB,OAAO,OAAO,GAAS,WAClB,EACD,IACN,ECXW,MAEL,OAAO,OAAW,IAAoB,KAExC,OAOA,WAAW,SAAS,WAAW,WACjB,KCXP,EACX,GASA,OAAO,EAAU,iBAAoB,YACrC,OAAO,EAAU,eAAkB,WCZxB,EAA+B,GAC1C,aAAiB,YAChB,EAAM,SAAS,SAAS,MAAM,GAC7B,EAAM,SAAS,SAAS,4BAA4B,GCIpD,EAA0B,QAAQ,QAAQ,EACxC,EAAe,IAAI,QACrB,EAA8B,GAa5B,EAAgB,MACpB,EACA,EACA,EACA,EACA,EACA,IACiC,CACjC,IAAI,EAAW,EACX,EAAc,GAElB,KAAO,EAAW,GAAY,CAE5B,GADA,IACI,EAAa,IAAI,CAAS,IAAM,EAClC,MAAO,CAAE,GAAI,GAAO,OAAQ,WAAY,EAC1C,GAAI,OAAO,EAAU,kBAAqB,WACxC,MAAO,CAAE,GAAI,GAAO,OAAQ,cAAe,EAI7C,IAAM,EAAO,EAA4B,EACzC,GAAI,EAAM,CACR,EAAc,GACd,GAAI,CACF,IAAM,EAAW,IAAI,EAAK,IAAA,GAAW,EAAK,EAC1C,GAAI,OAAO,EAAS,QAAW,WAE7B,OADA,MAAM,EAAS,OAAO,CAAS,EACxB,CAAE,GAAI,GAAM,SAAU,CAAE,CAEnC,MAAQ,CAER,CACF,CAEA,IAAM,EAAQ,EAA0B,EACxC,GAAI,GAAS,EAAgC,CAAK,EAAG,CACnD,EAAc,GACT,MAAM,QAAQ,EAAM,QAAQ,IAAG,EAAM,SAAW,CAAC,GAEtD,IAAI,EACJ,GAAI,CACF,EAAU,EAAM,gBAAgB,CAAS,CAC3C,OAAS,EAAO,CACd,EAAQ,EAAO,SAAS,EACxB,EAAU,CAAC,CACb,CAEA,GAAI,EAAQ,OAAS,EAAG,CACtB,IAAI,EAAW,EACf,IAAK,IAAM,KAAU,EAAS,CAC5B,GAAI,EAAa,IAAI,CAAS,IAAM,EAClC,MAAO,CAAE,GAAI,GAAO,OAAQ,WAAY,EAE1C,GAAI,CACF,EAAM,cAAc,KAAK,EAAO,EAAW,CAAM,EACjD,GACF,OAAS,EAAO,CACT,EAA4B,CAAK,GACpC,EAAQ,EAAO,eAAe,CAClC,CACF,CACA,MAAO,CAAE,GAAI,GAAM,UAAS,CAC9B,CACF,CAEA,GAAI,GAAY,EAAY,MAC5B,MAAM,IAAI,QAAS,GAAY,WAAW,EAAS,CAAU,CAAC,CAChE,CAYA,OAVK,EAUE,CAAE,GAAI,GAAO,OAAQ,YAAa,GATlC,IACH,EAA8B,GAC9B,EACM,MAAM,qDAAqD,EAC/D,qBACF,GAEK,CAAE,GAAI,GAAO,OAAQ,qBAAsB,EAGtD,EAca,GACX,EACA,EAAgC,CAAC,IACA,CACjC,GAAI,CAAC,EAAW,OAAO,QAAQ,QAAQ,CAAE,GAAI,GAAO,OAAQ,cAAe,CAAC,EAE5E,GAAM,CACJ,aAAa,GACb,aAAa,IACb,UAAU,EACV,YAAY,GACV,EAIE,GAAS,EAAa,IAAI,CAAS,GAAK,GAAK,EACnD,EAAa,IAAI,EAAW,CAAK,EAEjC,IAAM,EAAM,EAAM,SAChB,EAAc,EAAW,EAAW,EAAO,EAAY,EAAY,CAAO,CAC5E,EAMA,MAJA,GAAQ,EAAI,SACJ,IAAA,OACA,IAAA,EACR,EACO,CACT"}
@@ -0,0 +1,12 @@
1
+ import { Config } from 'dompurify';
2
+ /**
3
+ * DOMPurify custom-element handling that preserves embedded-widget custom
4
+ * elements (any hyphenated tag, e.g. `<news-teaser>`) and their kebab-case /
5
+ * `data-` attributes so the host widget manager (renderWidgets) can hydrate
6
+ * them. Without this, DOMPurify's default profile silently strips unknown custom
7
+ * elements AND their attributes, wiping embeds out of the content before they can
8
+ * render. `on*` event-handler attributes are still rejected (defense in depth
9
+ * alongside DOMPurify's own XSS stripping). Promoted from staffbase-global-content.
10
+ */
11
+ export declare const customElementHandling: NonNullable<Config['CUSTOM_ELEMENT_HANDLING']>;
12
+ //# sourceMappingURL=customElementHandling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customElementHandling.d.ts","sourceRoot":"","sources":["../../../src/html/customElementHandling.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAEvC;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,EAAE,WAAW,CAC7C,MAAM,CAAC,yBAAyB,CAAC,CAQlC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"sanitizeArticleHtml.d.ts","sourceRoot":"","sources":["../../../src/html/sanitizeArticleHtml.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0CAA0C,CAAA;AA2B1F;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,GAC9B,MAAM,MAAM,EACZ,UAAS,0BAA+B,KACvC,MAqBF,CAAA"}
1
+ {"version":3,"file":"sanitizeArticleHtml.d.ts","sourceRoot":"","sources":["../../../src/html/sanitizeArticleHtml.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0CAA0C,CAAA;AA4B1F;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,GAC9B,MAAM,MAAM,EACZ,UAAS,0BAA+B,KACvC,MAsBF,CAAA"}
@@ -2,7 +2,9 @@
2
2
  * Strict sanitizer for untrusted snippet/teaser HTML rendered through a
3
3
  * non-sanitizing parser (e.g. html-react-parser). The default DOMPurify profile
4
4
  * strips scripts, inline handlers and dangerous URLs AND drops iframes, keeping
5
- * only basic rich-text markup. From global-content's sanitizeHtml.
5
+ * only basic rich-text markup. Embedded-widget custom elements (and their
6
+ * kebab/data attributes) are preserved so the host can hydrate them. From
7
+ * global-content's sanitizeHtml.
6
8
  *
7
9
  * Use sanitizeArticleHtml instead when rendering full article bodies that must
8
10
  * keep iframes / data-* embeds.
@@ -1 +1 @@
1
- {"version":3,"file":"sanitizeHtml.d.ts","sourceRoot":"","sources":["../../../src/html/sanitizeHtml.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MAAkC,CAAA"}
1
+ {"version":3,"file":"sanitizeHtml.d.ts","sourceRoot":"","sources":["../../../src/html/sanitizeHtml.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MACkC,CAAA"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Options for renderWidgets.
3
+ */
4
+ export interface RenderWidgetsOptions {
5
+ /** Maximum render attempts before giving up. Default 10. */
6
+ maxRetries?: number;
7
+ /** Delay between attempts, in milliseconds. Default 300. */
8
+ retryDelay?: number;
9
+ /**
10
+ * Called when an error is swallowed. `context` labels where it happened
11
+ * (e.g. 'manager-unavailable', 'extract', 'render-widget'). Default: warn on
12
+ * critical context only.
13
+ */
14
+ onError?: (error: unknown, context: string) => void;
15
+ /**
16
+ * Token used to dedupe/cancel repeated renders. A newer render with the same
17
+ * key cancels older in-flight/queued ones. Default: the container element.
18
+ */
19
+ cancelKey?: object;
20
+ }
21
+ //# sourceMappingURL=RenderWidgetsOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RenderWidgetsOptions.d.ts","sourceRoot":"","sources":["../../../../src/types/widgets/RenderWidgetsOptions.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACnD;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Result of a renderWidgets call. `rendered` is the number of widgets rendered
3
+ * via the prototype path; it is 0 when the host constructor path handled
4
+ * rendering (the count is not observable there).
5
+ */
6
+ export type RenderWidgetsResult = {
7
+ ok: true;
8
+ rendered: number;
9
+ } | {
10
+ ok: false;
11
+ reason: 'no-container' | 'manager-unavailable' | 'no-widgets' | 'cancelled';
12
+ };
13
+ //# sourceMappingURL=RenderWidgetsResult.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RenderWidgetsResult.d.ts","sourceRoot":"","sources":["../../../../src/types/widgets/RenderWidgetsResult.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC9B;IACE,EAAE,EAAE,KAAK,CAAA;IACT,MAAM,EACF,cAAc,GACd,qBAAqB,GACrB,YAAY,GACZ,WAAW,CAAA;CAChB,CAAA"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The host's widget-manager constructor (`window.staffbase.content.widgetMgr`).
3
+ * The signature is internal; most builds accept `(unused, isEditor?)` and the
4
+ * instance exposes a `render(container)` method.
5
+ */
6
+ export type StaffbaseWidgetManagerConstructor = new (...args: unknown[]) => {
7
+ render?: (container: HTMLElement) => Promise<void> | void;
8
+ };
9
+ //# sourceMappingURL=StaffbaseWidgetManagerConstructor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StaffbaseWidgetManagerConstructor.d.ts","sourceRoot":"","sources":["../../../../src/types/widgets/StaffbaseWidgetManagerConstructor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,iCAAiC,GAAG,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK;IAC1E,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAC1D,CAAA"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The host's private widget-manager prototype
3
+ * (`window.staffbase.content.widgetMgr.prototype`). All members are optional
4
+ * because this is an undocumented API that may change.
5
+ */
6
+ export interface StaffbaseWidgetManagerPrototype {
7
+ _widgets?: unknown[];
8
+ _extractWidgets?: (container: HTMLElement) => unknown[];
9
+ _renderWidget?: (this: StaffbaseWidgetManagerPrototype, container: HTMLElement, widget: unknown) => void;
10
+ }
11
+ //# sourceMappingURL=StaffbaseWidgetManagerPrototype.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StaffbaseWidgetManagerPrototype.d.ts","sourceRoot":"","sources":["../../../../src/types/widgets/StaffbaseWidgetManagerPrototype.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;IACpB,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,WAAW,KAAK,OAAO,EAAE,CAAA;IACvD,aAAa,CAAC,EAAE,CACd,IAAI,EAAE,+BAA+B,EACrC,SAAS,EAAE,WAAW,EACtB,MAAM,EAAE,OAAO,KACZ,IAAI,CAAA;CACV"}
@@ -0,0 +1,16 @@
1
+ import { DependencyList } from 'react';
2
+ import { RenderWidgetsOptions } from './RenderWidgetsOptions';
3
+ /**
4
+ * Options for the useRenderWidgets hook: renderWidgets options plus optional
5
+ * MutationObserver wiring.
6
+ */
7
+ export interface UseRenderWidgetsOptions extends RenderWidgetsOptions {
8
+ /**
9
+ * When true, a debounced MutationObserver (childList + subtree) re-renders on
10
+ * article HTML changes (accordions, "View more"), cleaned up on unmount.
11
+ */
12
+ observe?: boolean;
13
+ /** Effect dependency list controlling when a render re-runs. Default []. */
14
+ deps?: DependencyList;
15
+ }
16
+ //# sourceMappingURL=UseRenderWidgetsOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UseRenderWidgetsOptions.d.ts","sourceRoot":"","sources":["../../../../src/types/widgets/UseRenderWidgetsOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAE3C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAElE;;;GAGG;AACH,MAAM,WAAW,uBAAwB,SAAQ,oBAAoB;IACnE;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,cAAc,CAAA;CACtB"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Default onError for renderWidgets: warns only on the critical
3
+ * 'manager-unavailable' context so a platform regression stays visible in logs
4
+ * without spamming per-widget noise. Consumers can pass their own onError.
5
+ * @param {unknown} error - The swallowed error.
6
+ * @param {string} context - Where it happened.
7
+ * @returns {void}
8
+ */
9
+ export declare const defaultWidgetOnError: (error: unknown, context: string) => void;
10
+ //# sourceMappingURL=defaultWidgetOnError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultWidgetOnError.d.ts","sourceRoot":"","sources":["../../../src/widgets/defaultWidgetOnError.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,OAAO,OAAO,EAAE,SAAS,MAAM,KAAG,IAOtE,CAAA"}
@@ -0,0 +1,8 @@
1
+ import { StaffbaseWidgetManagerConstructor } from '../types/widgets/StaffbaseWidgetManagerConstructor';
2
+ /**
3
+ * Resolves the host widget-manager constructor from the global Staffbase object,
4
+ * or null when it is not a function (older runtimes / platform regression).
5
+ * @returns {StaffbaseWidgetManagerConstructor | null} The constructor or null.
6
+ */
7
+ export declare const getWidgetManagerConstructor: () => StaffbaseWidgetManagerConstructor | null;
8
+ //# sourceMappingURL=getWidgetManagerConstructor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getWidgetManagerConstructor.d.ts","sourceRoot":"","sources":["../../../src/widgets/getWidgetManagerConstructor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,oDAAoD,CAAA;AAE3G;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,QAClC,iCAAiC,GAAG,IAUvC,CAAA"}
@@ -0,0 +1,8 @@
1
+ import { StaffbaseWidgetManagerPrototype } from '../types/widgets/StaffbaseWidgetManagerPrototype';
2
+ /**
3
+ * Resolves the host widget-manager prototype (the private fallback API) from the
4
+ * global Staffbase object, or null when it is unavailable.
5
+ * @returns {StaffbaseWidgetManagerPrototype | null} The prototype or null.
6
+ */
7
+ export declare const getWidgetManagerPrototype: () => StaffbaseWidgetManagerPrototype | null;
8
+ //# sourceMappingURL=getWidgetManagerPrototype.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getWidgetManagerPrototype.d.ts","sourceRoot":"","sources":["../../../src/widgets/getWidgetManagerPrototype.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,kDAAkD,CAAA;AAEvG;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,QAChC,+BAA+B,GAAG,IAYrC,CAAA"}
@@ -0,0 +1,12 @@
1
+ import { StaffbaseWidgetManagerPrototype } from '../types/widgets/StaffbaseWidgetManagerPrototype';
2
+ /**
3
+ * Type guard asserting the widget-manager prototype exposes the methods the
4
+ * prototype render path needs.
5
+ * @param {StaffbaseWidgetManagerPrototype} widgetMgr - The resolved prototype.
6
+ * @returns {boolean} True when both _extractWidgets and _renderWidget are functions.
7
+ */
8
+ export declare const hasRequiredWidgetManagerMethods: (widgetMgr: StaffbaseWidgetManagerPrototype) => widgetMgr is StaffbaseWidgetManagerPrototype & {
9
+ _extractWidgets: (container: HTMLElement) => unknown[];
10
+ _renderWidget: (this: StaffbaseWidgetManagerPrototype, container: HTMLElement, widget: unknown) => void;
11
+ };
12
+ //# sourceMappingURL=hasRequiredWidgetManagerMethods.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hasRequiredWidgetManagerMethods.d.ts","sourceRoot":"","sources":["../../../src/widgets/hasRequiredWidgetManagerMethods.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,kDAAkD,CAAA;AAEvG;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B,GAC1C,WAAW,+BAA+B,KACzC,SAAS,IAAI,+BAA+B,GAAG;IAChD,eAAe,EAAE,CAAC,SAAS,EAAE,WAAW,KAAK,OAAO,EAAE,CAAA;IACtD,aAAa,EAAE,CACb,IAAI,EAAE,+BAA+B,EACrC,SAAS,EAAE,WAAW,EACtB,MAAM,EAAE,OAAO,KACZ,IAAI,CAAA;CAGoC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export { renderWidgets } from './renderWidgets';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/widgets/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Detects known, benign internal Staffbase errors thrown by `_renderWidget` for
3
+ * individual widgets, so they can be swallowed without aborting the batch.
4
+ * Promoted from unacknowledged-bulletins (`each` / `undefined is not an object`).
5
+ * @param {unknown} error - The thrown value.
6
+ * @returns {boolean} True when the error is a known internal render error.
7
+ */
8
+ export declare const isKnownStaffbaseRenderError: (error: unknown) => boolean;
9
+ //# sourceMappingURL=isKnownStaffbaseRenderError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isKnownStaffbaseRenderError.d.ts","sourceRoot":"","sources":["../../../src/widgets/isKnownStaffbaseRenderError.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,GAAI,OAAO,OAAO,KAAG,OAGH,CAAA"}
@@ -0,0 +1,2 @@
1
+ export { useRenderWidgets } from './useRenderWidgets';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/widgets/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA"}
@@ -0,0 +1,14 @@
1
+ import { RefObject } from 'react';
2
+ import { UseRenderWidgetsOptions } from '../../types/widgets/UseRenderWidgetsOptions';
3
+ /**
4
+ * React adapter for renderWidgets. Renders on mount and whenever `deps` change,
5
+ * using `ref.current` as the cancelKey. With `observe: true` it mounts a
6
+ * debounced MutationObserver (childList + subtree) that re-renders on article
7
+ * HTML changes and cleans up on unmount. Absorbs the render/observe glue that
8
+ * the widgets repeat in their Article loaders.
9
+ * @param {RefObject<HTMLElement | null>} ref - Ref to the widget container.
10
+ * @param {UseRenderWidgetsOptions} options - renderWidgets options plus observe/deps.
11
+ * @returns {void}
12
+ */
13
+ export declare const useRenderWidgets: (ref: RefObject<HTMLElement | null>, options?: UseRenderWidgetsOptions) => void;
14
+ //# sourceMappingURL=useRenderWidgets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRenderWidgets.d.ts","sourceRoot":"","sources":["../../../../src/widgets/react/useRenderWidgets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,6CAA6C,CAAA;AAG1F;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAC3B,KAAK,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,EAClC,UAAS,uBAA4B,KACpC,IAwBF,CAAA"}
@@ -0,0 +1,16 @@
1
+ import { RenderWidgetsOptions } from '../types/widgets/RenderWidgetsOptions';
2
+ import { RenderWidgetsResult } from '../types/widgets/RenderWidgetsResult';
3
+ /**
4
+ * Renders the widgets embedded in `container` using the host's private widget
5
+ * manager. Superset of the four widget services: constructor path first with a
6
+ * prototype fallback, a global queue that serializes renders, per-cancelKey
7
+ * cancellation of superseded renders, configurable retries, swallowing of known
8
+ * internal Staffbase errors, and a once-per-session warning when the manager is
9
+ * missing. Returns a typed result so callers can react (e.g. mark a
10
+ * data-widget-render-error attribute).
11
+ * @param {HTMLElement | null} container - The element whose embedded widgets are rendered.
12
+ * @param {RenderWidgetsOptions} options - Retry, error and cancellation options.
13
+ * @returns {Promise<RenderWidgetsResult>} The typed render result.
14
+ */
15
+ export declare const renderWidgets: (container: HTMLElement | null, options?: RenderWidgetsOptions) => Promise<RenderWidgetsResult>;
16
+ //# sourceMappingURL=renderWidgets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderWidgets.d.ts","sourceRoot":"","sources":["../../../src/widgets/renderWidgets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAA;AACjF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAA;AA8G/E;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa,GACxB,WAAW,WAAW,GAAG,IAAI,EAC7B,UAAS,oBAAyB,KACjC,OAAO,CAAC,mBAAmB,CAwB7B,CAAA"}
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../renderWidgets-EbJq9Wn6.js");let t=require("react");var n=(n,r={})=>{let{observe:i=!1,deps:a=[],...o}=r;(0,t.useEffect)(()=>{let t=n.current;if(!t)return;let r={...o,cancelKey:t};if(e.t(t,r),!i)return;let a,s=new MutationObserver(()=>{clearTimeout(a),a=setTimeout(()=>void e.t(n.current,r),100)});return s.observe(t,{childList:!0,subtree:!0}),()=>{clearTimeout(a),s.disconnect()}},a)};exports.useRenderWidgets=n;
2
+ //# sourceMappingURL=react.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.cjs.js","names":[],"sources":["../../src/widgets/react/useRenderWidgets.ts"],"sourcesContent":["import type { RefObject } from 'react'\nimport { useEffect } from 'react'\n\nimport type { UseRenderWidgetsOptions } from '../../types/widgets/UseRenderWidgetsOptions'\nimport { renderWidgets } from '../renderWidgets'\n\n/**\n * React adapter for renderWidgets. Renders on mount and whenever `deps` change,\n * using `ref.current` as the cancelKey. With `observe: true` it mounts a\n * debounced MutationObserver (childList + subtree) that re-renders on article\n * HTML changes and cleans up on unmount. Absorbs the render/observe glue that\n * the widgets repeat in their Article loaders.\n * @param {RefObject<HTMLElement | null>} ref - Ref to the widget container.\n * @param {UseRenderWidgetsOptions} options - renderWidgets options plus observe/deps.\n * @returns {void}\n */\nexport const useRenderWidgets = (\n ref: RefObject<HTMLElement | null>,\n options: UseRenderWidgetsOptions = {},\n): void => {\n const { observe = false, deps = [], ...renderOptions } = options\n\n useEffect(() => {\n const el = ref.current\n if (!el) return\n\n const opts = { ...renderOptions, cancelKey: el }\n void renderWidgets(el, opts)\n\n if (!observe) return\n\n let timer: ReturnType<typeof setTimeout>\n const observer = new MutationObserver(() => {\n clearTimeout(timer)\n timer = setTimeout(() => void renderWidgets(ref.current, opts), 100)\n })\n observer.observe(el, { childList: true, subtree: true })\n\n return () => {\n clearTimeout(timer)\n observer.disconnect()\n }\n }, deps)\n}\n"],"mappings":"0IAgBA,IAAa,GACX,EACA,EAAmC,CAAC,IAC3B,CACT,GAAM,CAAE,UAAU,GAAO,OAAO,CAAC,EAAG,GAAG,GAAkB,GAEzD,EAAA,EAAA,eAAgB,CACd,IAAM,EAAK,EAAI,QACf,GAAI,CAAC,EAAI,OAET,IAAM,EAAO,CAAE,GAAG,EAAe,UAAW,CAAG,EAG/C,GAFA,EAAK,EAAc,EAAI,CAAI,EAEvB,CAAC,EAAS,OAEd,IAAI,EACE,EAAW,IAAI,qBAAuB,CAC1C,aAAa,CAAK,EAClB,EAAQ,eAAiB,KAAK,EAAA,EAAc,EAAI,QAAS,CAAI,EAAG,GAAG,CACrE,CAAC,EAGD,OAFA,EAAS,QAAQ,EAAI,CAAE,UAAW,GAAM,QAAS,EAAK,CAAC,MAE1C,CACX,aAAa,CAAK,EAClB,EAAS,WAAW,CACtB,CACF,EAAG,CAAI,CACT"}
@@ -0,0 +1,28 @@
1
+ import { t as e } from "../renderWidgets-CeIczubt.mjs";
2
+ import { useEffect as t } from "react";
3
+ //#region src/widgets/react/useRenderWidgets.ts
4
+ var n = (n, r = {}) => {
5
+ let { observe: i = !1, deps: a = [], ...o } = r;
6
+ t(() => {
7
+ let t = n.current;
8
+ if (!t) return;
9
+ let r = {
10
+ ...o,
11
+ cancelKey: t
12
+ };
13
+ if (e(t, r), !i) return;
14
+ let a, s = new MutationObserver(() => {
15
+ clearTimeout(a), a = setTimeout(() => void e(n.current, r), 100);
16
+ });
17
+ return s.observe(t, {
18
+ childList: !0,
19
+ subtree: !0
20
+ }), () => {
21
+ clearTimeout(a), s.disconnect();
22
+ };
23
+ }, a);
24
+ };
25
+ //#endregion
26
+ export { n as useRenderWidgets };
27
+
28
+ //# sourceMappingURL=react.es.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.es.mjs","names":[],"sources":["../../src/widgets/react/useRenderWidgets.ts"],"sourcesContent":["import type { RefObject } from 'react'\nimport { useEffect } from 'react'\n\nimport type { UseRenderWidgetsOptions } from '../../types/widgets/UseRenderWidgetsOptions'\nimport { renderWidgets } from '../renderWidgets'\n\n/**\n * React adapter for renderWidgets. Renders on mount and whenever `deps` change,\n * using `ref.current` as the cancelKey. With `observe: true` it mounts a\n * debounced MutationObserver (childList + subtree) that re-renders on article\n * HTML changes and cleans up on unmount. Absorbs the render/observe glue that\n * the widgets repeat in their Article loaders.\n * @param {RefObject<HTMLElement | null>} ref - Ref to the widget container.\n * @param {UseRenderWidgetsOptions} options - renderWidgets options plus observe/deps.\n * @returns {void}\n */\nexport const useRenderWidgets = (\n ref: RefObject<HTMLElement | null>,\n options: UseRenderWidgetsOptions = {},\n): void => {\n const { observe = false, deps = [], ...renderOptions } = options\n\n useEffect(() => {\n const el = ref.current\n if (!el) return\n\n const opts = { ...renderOptions, cancelKey: el }\n void renderWidgets(el, opts)\n\n if (!observe) return\n\n let timer: ReturnType<typeof setTimeout>\n const observer = new MutationObserver(() => {\n clearTimeout(timer)\n timer = setTimeout(() => void renderWidgets(ref.current, opts), 100)\n })\n observer.observe(el, { childList: true, subtree: true })\n\n return () => {\n clearTimeout(timer)\n observer.disconnect()\n }\n }, deps)\n}\n"],"mappings":";;;AAgBA,IAAa,KACX,GACA,IAAmC,CAAC,MAC3B;CACT,IAAM,EAAE,aAAU,IAAO,UAAO,CAAC,GAAG,GAAG,MAAkB;CAEzD,QAAgB;EACd,IAAM,IAAK,EAAI;EACf,IAAI,CAAC,GAAI;EAET,IAAM,IAAO;GAAE,GAAG;GAAe,WAAW;EAAG;EAG/C,IAFA,EAAmB,GAAI,CAAI,GAEvB,CAAC,GAAS;EAEd,IAAI,GACE,IAAW,IAAI,uBAAuB;GAE1C,AADA,aAAa,CAAK,GAClB,IAAQ,iBAAiB,KAAK,EAAc,EAAI,SAAS,CAAI,GAAG,GAAG;EACrE,CAAC;EAGD,OAFA,EAAS,QAAQ,GAAI;GAAE,WAAW;GAAM,SAAS;EAAK,CAAC,SAE1C;GAEX,AADA,aAAa,CAAK,GAClB,EAAS,WAAW;EACtB;CACF,GAAG,CAAI;AACT"}
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./renderWidgets-EbJq9Wn6.js");exports.renderWidgets=e.t;
@@ -0,0 +1,2 @@
1
+ import { t as e } from "./renderWidgets-CeIczubt.mjs";
2
+ export { e as renderWidgets };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@favish/staffbase-utils",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Shared internal/host utilities for Staffbase widgets",
5
5
  "author": "Favish <dev@favish.com>",
6
6
  "license": "UNLICENSED",
@@ -27,6 +27,16 @@
27
27
  "types": "./dist/src/links/index.d.ts",
28
28
  "import": "./dist/links.es.mjs",
29
29
  "require": "./dist/links.cjs.js"
30
+ },
31
+ "./widgets": {
32
+ "types": "./dist/src/widgets/index.d.ts",
33
+ "import": "./dist/widgets.es.mjs",
34
+ "require": "./dist/widgets.cjs.js"
35
+ },
36
+ "./widgets/react": {
37
+ "types": "./dist/src/widgets/react/index.d.ts",
38
+ "import": "./dist/widgets/react.es.mjs",
39
+ "require": "./dist/widgets/react.cjs.js"
30
40
  }
31
41
  },
32
42
  "repository": {
@@ -55,6 +65,7 @@
55
65
  "@testing-library/jest-dom": "^6.9.1",
56
66
  "@types/jest": "^30.0.0",
57
67
  "@types/node": "^25",
68
+ "@types/react": "19.2.16",
58
69
  "@typescript-eslint/eslint-plugin": "^8.60.0",
59
70
  "@typescript-eslint/parser": "^8.60.0",
60
71
  "eslint": "^10.4.0",