sibujs 4.11.0 → 4.12.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 (79) hide show
  1. package/README.md +2 -2
  2. package/dist/browser.cjs +1 -1
  3. package/dist/browser.js +4 -4
  4. package/dist/build.cjs +19 -1
  5. package/dist/build.js +13 -12
  6. package/dist/cdn.dev.global.js +12 -12
  7. package/dist/cdn.full.dev.global.js +13 -13
  8. package/dist/cdn.full.global.js +12 -12
  9. package/dist/cdn.global.js +12 -12
  10. package/dist/{chunk-FC6KCJSA.js → chunk-2F4PSJ5Z.js} +162 -67
  11. package/dist/{chunk-R244COCR.js → chunk-653KGQS2.js} +3 -3
  12. package/dist/chunk-AM6DZLBM.js +181 -0
  13. package/dist/{chunk-ADWDDJ5M.js → chunk-B2Y5BJSP.js} +1 -1
  14. package/dist/{chunk-KREPZR2U.js → chunk-CI4W6XAU.js} +4 -4
  15. package/dist/{chunk-NBBFF5VA.js → chunk-DFZL3AX7.js} +6 -6
  16. package/dist/{chunk-OJV2RW4D.js → chunk-EMB5PG7V.js} +1 -1
  17. package/dist/{chunk-3ACAAQIN.js → chunk-F2UWOZ6J.js} +4 -0
  18. package/dist/{chunk-REOAVT2M.js → chunk-HXT2RNH5.js} +1 -1
  19. package/dist/{chunk-F6B5CKF7.js → chunk-IHA62NGJ.js} +1 -1
  20. package/dist/{chunk-VZDMHDVA.js → chunk-LEUEKK4G.js} +2 -2
  21. package/dist/{chunk-WQ7M57JJ.js → chunk-LXBMVGVB.js} +3 -3
  22. package/dist/{chunk-RRKGQKPU.js → chunk-LYTUTXQP.js} +4 -4
  23. package/dist/{chunk-5IODQRR3.js → chunk-NAVLCP4S.js} +4 -4
  24. package/dist/{chunk-YXJHLMRH.js → chunk-NB7TFSJE.js} +5 -5
  25. package/dist/{chunk-5G53CC6L.js → chunk-QLG5IK7F.js} +4 -4
  26. package/dist/{chunk-KFJUMC3Q.js → chunk-RK7EPQPG.js} +1 -1
  27. package/dist/{chunk-LQ2C44YH.js → chunk-T5O2CUHV.js} +1 -1
  28. package/dist/{chunk-CHQPJY47.js → chunk-WFRQ6D7H.js} +1 -1
  29. package/dist/{chunk-3Z7AEJMQ.js → chunk-WH4U2VUE.js} +5 -3
  30. package/dist/{chunk-HMOPAC2Z.js → chunk-XX4BRMWF.js} +15 -3
  31. package/dist/{chunk-7V7VFT7E.js → chunk-ZJCHY2IB.js} +1 -1
  32. package/dist/{chunk-MCPL5WKQ.js → chunk-ZPY2UAKM.js} +3 -3
  33. package/dist/{chunk-DEUONJYR.js → chunk-ZTYCCBU3.js} +19 -11
  34. package/dist/data.cjs +1 -1
  35. package/dist/data.js +5 -5
  36. package/dist/devtools.cjs +1 -1
  37. package/dist/devtools.js +4 -4
  38. package/dist/ecosystem.cjs +9 -1
  39. package/dist/ecosystem.d.cts +1 -1
  40. package/dist/ecosystem.d.ts +1 -1
  41. package/dist/ecosystem.js +8 -8
  42. package/dist/extras.cjs +368 -154
  43. package/dist/extras.d.cts +2 -2
  44. package/dist/extras.d.ts +2 -2
  45. package/dist/extras.js +23 -22
  46. package/dist/index.cjs +19 -1
  47. package/dist/index.d.cts +91 -90
  48. package/dist/index.d.ts +91 -90
  49. package/dist/index.js +13 -12
  50. package/dist/motion.cjs +1 -1
  51. package/dist/motion.js +3 -3
  52. package/dist/patterns.cjs +1 -1
  53. package/dist/patterns.js +5 -5
  54. package/dist/performance.cjs +1 -1
  55. package/dist/performance.d.cts +2 -2
  56. package/dist/performance.d.ts +2 -2
  57. package/dist/performance.js +7 -6
  58. package/dist/plugins.cjs +344 -109
  59. package/dist/plugins.d.cts +1 -1
  60. package/dist/plugins.d.ts +1 -1
  61. package/dist/plugins.js +15 -12
  62. package/dist/{ssr-C8hgIAw7.d.cts → ssr-Db0Sgdu3.d.cts} +7 -4
  63. package/dist/{ssr-C8hgIAw7.d.ts → ssr-Db0Sgdu3.d.ts} +7 -4
  64. package/dist/{ssr-5OWAH3U2.js → ssr-VBM6J5SJ.js} +4 -2
  65. package/dist/ssr.cjs +346 -128
  66. package/dist/ssr.d.cts +1 -1
  67. package/dist/ssr.d.ts +1 -1
  68. package/dist/ssr.js +10 -9
  69. package/dist/{tagFactory-BL2fymez.d.ts → tagFactory-Bnd_Nph9.d.ts} +1 -1
  70. package/dist/{tagFactory-8qL9LCIx.d.cts → tagFactory-DkytnJws.d.cts} +1 -1
  71. package/dist/testing.cjs +1 -1
  72. package/dist/testing.js +2 -2
  73. package/dist/ui.cjs +251 -24
  74. package/dist/ui.d.cts +111 -1
  75. package/dist/ui.d.ts +111 -1
  76. package/dist/ui.js +130 -8
  77. package/dist/widgets.cjs +1 -1
  78. package/dist/widgets.js +6 -6
  79. package/package.json +1 -1
package/dist/ssr.js CHANGED
@@ -23,9 +23,9 @@ import {
23
23
  wasm,
24
24
  worker,
25
25
  workerFn
26
- } from "./chunk-YXJHLMRH.js";
26
+ } from "./chunk-NB7TFSJE.js";
27
27
  import "./chunk-52XFPGSN.js";
28
- import "./chunk-LQ2C44YH.js";
28
+ import "./chunk-T5O2CUHV.js";
29
29
  import {
30
30
  collectStream,
31
31
  deserializeState,
@@ -45,17 +45,18 @@ import {
45
45
  ssrSuspense,
46
46
  suspenseSwapScript,
47
47
  trustHTML
48
- } from "./chunk-FC6KCJSA.js";
48
+ } from "./chunk-2F4PSJ5Z.js";
49
+ import "./chunk-AM6DZLBM.js";
49
50
  import "./chunk-62BDYGAG.js";
50
- import "./chunk-3Z7AEJMQ.js";
51
- import "./chunk-HMOPAC2Z.js";
52
- import "./chunk-3ACAAQIN.js";
51
+ import "./chunk-WH4U2VUE.js";
52
+ import "./chunk-XX4BRMWF.js";
53
+ import "./chunk-F2UWOZ6J.js";
53
54
  import "./chunk-7ZHH77QA.js";
54
- import "./chunk-OJV2RW4D.js";
55
+ import "./chunk-EMB5PG7V.js";
55
56
  import "./chunk-EXXSN3BW.js";
56
57
  import "./chunk-7XHATCIH.js";
57
- import "./chunk-F6B5CKF7.js";
58
- import "./chunk-ADWDDJ5M.js";
58
+ import "./chunk-IHA62NGJ.js";
59
+ import "./chunk-B2Y5BJSP.js";
59
60
  import "./chunk-NYNYSPK7.js";
60
61
  export {
61
62
  Head,
@@ -93,7 +93,7 @@ type TagEventHandlers = {
93
93
  * it types `onElement`.
94
94
  */
95
95
  interface TagProps<El extends Element = HTMLElement> {
96
- id?: string | (() => string);
96
+ id?: string | (() => string | null | undefined);
97
97
  class?: string | (() => string | null | undefined | false) | Record<string, boolean | (() => boolean)>;
98
98
  style?: StyleMap | string | (() => string | StyleObject);
99
99
  /**
@@ -93,7 +93,7 @@ type TagEventHandlers = {
93
93
  * it types `onElement`.
94
94
  */
95
95
  interface TagProps<El extends Element = HTMLElement> {
96
- id?: string | (() => string);
96
+ id?: string | (() => string | null | undefined);
97
97
  class?: string | (() => string | null | undefined | false) | Record<string, boolean | (() => boolean)>;
98
98
  style?: StyleMap | string | (() => string | StyleObject);
99
99
  /**
package/dist/testing.cjs CHANGED
@@ -780,7 +780,7 @@ function forEachSubscriber(signal, visit) {
780
780
  }
781
781
 
782
782
  // src/reactivity/track.ts
783
- var _runtimeVersion = true ? "4.11.0" : "dev";
783
+ var _runtimeVersion = true ? "4.12.0" : "dev";
784
784
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
785
785
  function resolveReactiveApi() {
786
786
  const g = globalThis;
package/dist/testing.js CHANGED
@@ -3,9 +3,9 @@ import {
3
3
  } from "./chunk-7ZHH77QA.js";
4
4
  import {
5
5
  effect
6
- } from "./chunk-OJV2RW4D.js";
6
+ } from "./chunk-EMB5PG7V.js";
7
7
  import "./chunk-7XHATCIH.js";
8
- import "./chunk-ADWDDJ5M.js";
8
+ import "./chunk-B2Y5BJSP.js";
9
9
  import {
10
10
  dispose,
11
11
  replaceChildrenSafely,
package/dist/ui.cjs CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // ui.ts
21
21
  var ui_exports = {};
22
22
  __export(ui_exports, {
23
+ DEFAULT_EMBED_SANDBOX: () => DEFAULT_EMBED_SANDBOX,
23
24
  FocusTrap: () => FocusTrap,
24
25
  RenderProp: () => RenderProp,
25
26
  VirtualList: () => VirtualList,
@@ -46,6 +47,7 @@ __export(ui_exports, {
46
47
  defineStrictComponent: () => defineStrictComponent,
47
48
  dialog: () => dialog,
48
49
  email: () => email,
50
+ embed: () => embed,
49
51
  eventBus: () => eventBus,
50
52
  focus: () => focus,
51
53
  form: () => form,
@@ -69,6 +71,7 @@ __export(ui_exports, {
69
71
  required: () => required,
70
72
  scopedStyle: () => scopedStyle,
71
73
  scrollLock: () => scrollLock,
74
+ setEmbedPolicy: () => setEmbedPolicy,
72
75
  ssnMask: () => ssnMask,
73
76
  svgElement: () => svgElement,
74
77
  timeMask: () => timeMask,
@@ -860,7 +863,7 @@ function forEachSubscriber(signal2, visit) {
860
863
  }
861
864
 
862
865
  // src/reactivity/track.ts
863
- var _runtimeVersion = true ? "4.11.0" : "dev";
866
+ var _runtimeVersion = true ? "4.12.0" : "dev";
864
867
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
865
868
  function resolveReactiveApi() {
866
869
  const g = globalThis;
@@ -1093,7 +1096,7 @@ var isBatching = API2.isBatching;
1093
1096
  // src/core/signals/signal.ts
1094
1097
  var _g = globalThis;
1095
1098
  function signal(initial, options) {
1096
- const state2 = {
1099
+ const state3 = {
1097
1100
  value: initial,
1098
1101
  __v: 0,
1099
1102
  __sc: 0,
@@ -1106,57 +1109,57 @@ function signal(initial, options) {
1106
1109
  };
1107
1110
  const debugName = DEV ? options?.name : void 0;
1108
1111
  const equalsFn = options?.equals;
1109
- if (debugName) state2.__name = debugName;
1112
+ if (debugName) state3.__name = debugName;
1110
1113
  function get() {
1111
- recordDependency2(state2);
1112
- return state2.value;
1114
+ recordDependency2(state3);
1115
+ return state3.value;
1113
1116
  }
1114
- get.__signal = state2;
1117
+ get.__signal = state3;
1115
1118
  if (debugName) get.__name = debugName;
1116
1119
  let set;
1117
1120
  if (equalsFn) {
1118
1121
  set = (next) => {
1119
- const prev = state2.value;
1122
+ const prev = state3.value;
1120
1123
  const newValue = typeof next === "function" ? next(prev) : next;
1121
1124
  if (equalsFn(prev, newValue)) return;
1122
- state2.value = newValue;
1123
- state2.__v++;
1125
+ state3.value = newValue;
1126
+ state3.__v++;
1124
1127
  if (DEV) {
1125
1128
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1126
- if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1129
+ if (hook) emitDevtools(hook, "signal:update", { signal: state3, name: debugName, oldValue: prev, newValue });
1127
1130
  }
1128
- if (!enqueueBatchedSignal(state2)) {
1129
- notifySubscribers2(state2);
1131
+ if (!enqueueBatchedSignal(state3)) {
1132
+ notifySubscribers2(state3);
1130
1133
  }
1131
1134
  };
1132
1135
  } else if (DEV) {
1133
1136
  set = (next) => {
1134
- const prev = state2.value;
1137
+ const prev = state3.value;
1135
1138
  const newValue = typeof next === "function" ? next(prev) : next;
1136
1139
  if (Object.is(newValue, prev)) return;
1137
- state2.value = newValue;
1138
- state2.__v++;
1140
+ state3.value = newValue;
1141
+ state3.__v++;
1139
1142
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1140
- if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1141
- if (!enqueueBatchedSignal(state2)) {
1142
- notifySubscribers2(state2);
1143
+ if (hook) emitDevtools(hook, "signal:update", { signal: state3, name: debugName, oldValue: prev, newValue });
1144
+ if (!enqueueBatchedSignal(state3)) {
1145
+ notifySubscribers2(state3);
1143
1146
  }
1144
1147
  };
1145
1148
  } else {
1146
1149
  set = (next) => {
1147
- const prev = state2.value;
1150
+ const prev = state3.value;
1148
1151
  const newValue = typeof next === "function" ? next(prev) : next;
1149
1152
  if (Object.is(newValue, prev)) return;
1150
- state2.value = newValue;
1151
- state2.__v++;
1152
- if (!enqueueBatchedSignal(state2)) {
1153
- notifySubscribers2(state2);
1153
+ state3.value = newValue;
1154
+ state3.__v++;
1155
+ if (!enqueueBatchedSignal(state3)) {
1156
+ notifySubscribers2(state3);
1154
1157
  }
1155
1158
  };
1156
1159
  }
1157
1160
  if (DEV) {
1158
1161
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1159
- if (hook) emitDevtools(hook, "signal:create", { signal: state2, name: debugName, getter: get, initial });
1162
+ if (hook) emitDevtools(hook, "signal:create", { signal: state3, name: debugName, getter: get, initial });
1160
1163
  }
1161
1164
  return [get, set];
1162
1165
  }
@@ -3226,6 +3229,227 @@ function dialog() {
3226
3229
  return { open, close, isOpen, toggle, dispose: dispose2 };
3227
3230
  }
3228
3231
 
3232
+ // src/core/rendering/requireDocument.ts
3233
+ function noDocument() {
3234
+ throw new Error(
3235
+ "[SibuJS] renderToString needs a DOM: register happy-dom or jsdom globals first." + (DEV ? ' There is no global `document`, and SibuJS builds live DOM nodes, on the server too. For example: import { GlobalRegistrator } from "@happy-dom/global-registrator"; GlobalRegistrator.register(); See SSR in AGENTS.md \xA710.' : "")
3236
+ );
3237
+ }
3238
+
3239
+ // src/utils/embedPolicy.ts
3240
+ var state2 = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.embed.v2"), () => ({
3241
+ allowed: [],
3242
+ frames: /* @__PURE__ */ new WeakMap()
3243
+ }));
3244
+ var REQUEST_CACHE = "embed";
3245
+ function currentEmbedAllowlist() {
3246
+ const scoped = getRequestStore()?.caches?.get(REQUEST_CACHE)?.get("allowed");
3247
+ return scoped ?? state2.allowed;
3248
+ }
3249
+ function setEmbedPolicy(policy) {
3250
+ const allowed = [];
3251
+ for (const entry of policy.allowedOrigins) {
3252
+ let url = null;
3253
+ try {
3254
+ url = new URL(entry);
3255
+ } catch {
3256
+ url = null;
3257
+ }
3258
+ if (!url || url.protocol !== "https:" || url.username || url.password || url.search || url.hash) {
3259
+ throw new TypeError(
3260
+ `setEmbedPolicy: "${entry}" is not an allowed embed origin. Use an absolute https: URL, optionally with a path prefix, and no credentials, query or fragment.`
3261
+ );
3262
+ }
3263
+ allowed.push({ origin: url.origin, path: url.pathname === "/" ? "" : url.pathname });
3264
+ }
3265
+ const store = getRequestStore();
3266
+ if (store) {
3267
+ const caches = store.caches ?? (store.caches = /* @__PURE__ */ new Map());
3268
+ let scoped = caches.get(REQUEST_CACHE);
3269
+ if (!scoped) {
3270
+ scoped = /* @__PURE__ */ new Map();
3271
+ caches.set(REQUEST_CACHE, scoped);
3272
+ }
3273
+ scoped.set("allowed", allowed);
3274
+ } else {
3275
+ state2.allowed = allowed;
3276
+ }
3277
+ }
3278
+ function resolveEmbedSrc(value, allowed = currentEmbedAllowlist()) {
3279
+ if (typeof value !== "string" || sanitizeUrl(value) === "") return null;
3280
+ let url;
3281
+ try {
3282
+ url = new URL(value);
3283
+ } catch {
3284
+ return null;
3285
+ }
3286
+ if (url.protocol !== "https:" || url.username || url.password) return null;
3287
+ if (typeof location !== "undefined" && url.origin === location.origin) return null;
3288
+ for (const entry of allowed) {
3289
+ if (url.origin !== entry.origin) continue;
3290
+ const path = entry.path;
3291
+ if (path === "" || url.pathname === path || url.pathname.startsWith(path.endsWith("/") ? path : `${path}/`)) {
3292
+ return url.href;
3293
+ }
3294
+ }
3295
+ return null;
3296
+ }
3297
+ var DEFAULT_EMBED_SANDBOX = [
3298
+ "allow-scripts",
3299
+ "allow-same-origin",
3300
+ "allow-presentation",
3301
+ "allow-popups",
3302
+ "allow-popups-to-escape-sandbox"
3303
+ ];
3304
+ var EMBED_FEATURES = /* @__PURE__ */ new Set([
3305
+ "accelerometer",
3306
+ "autoplay",
3307
+ "clipboard-write",
3308
+ "encrypted-media",
3309
+ "fullscreen",
3310
+ "gyroscope",
3311
+ "picture-in-picture",
3312
+ "web-share"
3313
+ ]);
3314
+ var EMBED_REFERRER_POLICIES = /* @__PURE__ */ new Set([
3315
+ "no-referrer",
3316
+ "same-origin",
3317
+ "strict-origin",
3318
+ "strict-origin-when-cross-origin"
3319
+ ]);
3320
+ var DEFAULT_EMBED_REFERRER_POLICY = "strict-origin-when-cross-origin";
3321
+ function narrowEmbedSandbox(value) {
3322
+ const out = [];
3323
+ for (const token of value.toLowerCase().split(/[\t\n\f\r ]+/)) {
3324
+ if (token && DEFAULT_EMBED_SANDBOX.includes(token) && !out.includes(token)) out.push(token);
3325
+ }
3326
+ return out.join(" ");
3327
+ }
3328
+ function narrowEmbedAllow(value) {
3329
+ const granted = [];
3330
+ const denied = [];
3331
+ for (const directive of value.split(";")) {
3332
+ const tokens = directive.trim().split(/[\t\n\f\r ]+/);
3333
+ const feature = tokens[0];
3334
+ if (!feature) continue;
3335
+ const list = tokens.slice(1).map((token) => token.toLowerCase());
3336
+ const grantsSrc = (list.length === 0 || list.includes("'src'") || list.includes("*")) && !list.includes("'none'");
3337
+ if (!grantsSrc) denied.push(feature);
3338
+ else if (EMBED_FEATURES.has(feature) && !granted.includes(feature)) granted.push(feature);
3339
+ }
3340
+ return granted.filter((feature) => !denied.includes(feature)).join("; ");
3341
+ }
3342
+ function narrowEmbedReferrerPolicy(value) {
3343
+ const lower = value.trim().toLowerCase();
3344
+ return EMBED_REFERRER_POLICIES.has(lower) ? lower : DEFAULT_EMBED_REFERRER_POLICY;
3345
+ }
3346
+ function recordEmbedFrame(frame, record) {
3347
+ state2.frames.set(frame, record);
3348
+ }
3349
+
3350
+ // src/ui/embed.ts
3351
+ var DEFAULT_ALLOW = "encrypted-media; picture-in-picture; fullscreen";
3352
+ var RATIO = /^\d+(\.\d+)?(\s*\/\s*\d+(\.\d+)?)?$/;
3353
+ var warnedEmbeds = /* @__PURE__ */ new Set();
3354
+ function warnEmbed(key, message) {
3355
+ devWarnLazy(() => {
3356
+ if (warnedEmbeds.has(key) || warnedEmbeds.size >= 100) return "";
3357
+ warnedEmbeds.add(key);
3358
+ return message;
3359
+ });
3360
+ }
3361
+ function embed(props) {
3362
+ const { src, title } = props;
3363
+ if (typeof title !== "function" && (typeof title !== "string" || !title.trim())) {
3364
+ throw new TypeError("embed: `title` is required \u2014 it is the frame's accessible name.");
3365
+ }
3366
+ if (typeof document === "undefined") noDocument();
3367
+ if ("srcdoc" in props) {
3368
+ warnEmbed("srcdoc", "embed: `srcdoc` is never written \u2014 a frame's document must come from an allowlisted src.");
3369
+ }
3370
+ const el = document.createElement("iframe");
3371
+ const allowed = currentEmbedAllowlist();
3372
+ const sandbox = props.sandbox === void 0 ? DEFAULT_EMBED_SANDBOX.join(" ") : narrowEmbedSandbox(props.sandbox);
3373
+ if (props.sandbox !== void 0 && sandbox !== props.sandbox.trim().toLowerCase().split(/\s+/).join(" ")) {
3374
+ warnEmbed(
3375
+ `sandbox|${props.sandbox}`,
3376
+ `embed: sandbox "${props.sandbox}" was narrowed to "${sandbox}". A sandbox may only remove tokens from DEFAULT_EMBED_SANDBOX (${DEFAULT_EMBED_SANDBOX.join(" ")}).`
3377
+ );
3378
+ }
3379
+ el.setAttribute("sandbox", sandbox);
3380
+ const allowIn = props.allow ?? DEFAULT_ALLOW;
3381
+ const allow = narrowEmbedAllow(allowIn);
3382
+ const requested = allowIn.split(";").map((d) => d.trim().toLowerCase()).filter(Boolean).join("; ");
3383
+ if (allow !== requested) {
3384
+ warnEmbed(
3385
+ `allow|${allowIn}`,
3386
+ `embed: allow "${allowIn}" was narrowed to "${allow}" \u2014 only player features are delegated.`
3387
+ );
3388
+ }
3389
+ if (allow) el.setAttribute("allow", allow);
3390
+ if (allow.split("; ").includes("fullscreen")) el.setAttribute("allowfullscreen", "");
3391
+ const referrer = narrowEmbedReferrerPolicy(props.referrerpolicy ?? DEFAULT_EMBED_REFERRER_POLICY);
3392
+ if (props.referrerpolicy !== void 0 && referrer !== props.referrerpolicy) {
3393
+ warnEmbed(
3394
+ `referrer|${props.referrerpolicy}`,
3395
+ `embed: referrerpolicy "${props.referrerpolicy}" is less strict than the default; using "${referrer}".`
3396
+ );
3397
+ }
3398
+ el.setAttribute("referrerpolicy", referrer);
3399
+ recordEmbedFrame(el, { allowed, sandbox, allow, referrer });
3400
+ el.setAttribute("loading", props.loading === "eager" ? "eager" : "lazy");
3401
+ if (props.aspectRatio !== void 0) {
3402
+ if (RATIO.test(props.aspectRatio.trim())) {
3403
+ el.style.setProperty("aspect-ratio", props.aspectRatio.trim());
3404
+ el.style.setProperty("width", "100%");
3405
+ el.style.setProperty("height", "auto");
3406
+ el.style.setProperty("border", "0");
3407
+ } else {
3408
+ warnEmbed(
3409
+ `ratio|${props.aspectRatio}`,
3410
+ `embed: aspectRatio "${props.aspectRatio}" is not a ratio such as "16 / 9".`
3411
+ );
3412
+ }
3413
+ }
3414
+ if (typeof props.class === "string") el.setAttribute("class", props.class);
3415
+ if (typeof props.id === "string") el.id = props.id;
3416
+ if (typeof title === "function") {
3417
+ registerDisposer(
3418
+ el,
3419
+ reactiveBinding2(() => {
3420
+ const value = String(title() ?? "");
3421
+ if (value.trim()) el.setAttribute("title", value);
3422
+ else warnEmbed("title", "embed: a `title` getter returned a blank value; the previous title is kept.");
3423
+ }, el)
3424
+ );
3425
+ } else {
3426
+ el.setAttribute("title", title);
3427
+ }
3428
+ const commitSrc = (value) => {
3429
+ const safe = resolveEmbedSrc(value, allowed);
3430
+ if (safe === null) {
3431
+ el.removeAttribute("src");
3432
+ if (value != null) {
3433
+ warnEmbed(
3434
+ `src|${String(value)}`,
3435
+ `embed: refusing src "${String(value)}" \u2014 it is not an https: URL on an origin allowed by setEmbedPolicy().`
3436
+ );
3437
+ }
3438
+ return;
3439
+ }
3440
+ if (el.getAttribute("src") !== safe) el.setAttribute("src", safe);
3441
+ };
3442
+ if (typeof src === "function") {
3443
+ registerDisposer(
3444
+ el,
3445
+ reactiveBinding2(() => commitSrc(src()), el)
3446
+ );
3447
+ } else {
3448
+ commitSrc(src);
3449
+ }
3450
+ return el;
3451
+ }
3452
+
3229
3453
  // src/ui/toast.ts
3230
3454
  function toast(options) {
3231
3455
  const duration = options?.duration ?? 3e3;
@@ -4133,6 +4357,7 @@ function createGuard(validator) {
4133
4357
  }
4134
4358
  // Annotate the CommonJS export names for ESM import in node:
4135
4359
  0 && (module.exports = {
4360
+ DEFAULT_EMBED_SANDBOX,
4136
4361
  FocusTrap,
4137
4362
  RenderProp,
4138
4363
  VirtualList,
@@ -4159,6 +4384,7 @@ function createGuard(validator) {
4159
4384
  defineStrictComponent,
4160
4385
  dialog,
4161
4386
  email,
4387
+ embed,
4162
4388
  eventBus,
4163
4389
  focus,
4164
4390
  form,
@@ -4182,6 +4408,7 @@ function createGuard(validator) {
4182
4408
  required,
4183
4409
  scopedStyle,
4184
4410
  scrollLock,
4411
+ setEmbedPolicy,
4185
4412
  ssnMask,
4186
4413
  svgElement,
4187
4414
  timeMask,
package/dist/ui.d.cts CHANGED
@@ -180,6 +180,116 @@ interface DialogAriaHandle {
180
180
  */
181
181
  declare function createDialogAria(element: HTMLElement, options?: DialogAriaOptions): DialogAriaHandle;
182
182
 
183
+ /**
184
+ * The policy `embed()` enforces.
185
+ *
186
+ * Each entry of `allowedOrigins` is an absolute `https:` URL: an origin
187
+ * (`"https://www.youtube-nocookie.com"`) allows every path on it; an origin
188
+ * with a path (`"https://player.vimeo.com/video/"`) allows that path and what
189
+ * lies beneath it. Credentials, a query or a fragment in an entry are refused.
190
+ */
191
+ interface EmbedPolicy {
192
+ allowedOrigins: readonly string[];
193
+ }
194
+ /**
195
+ * Set the origins `embed()` may frame. Replaces the previous policy; the
196
+ * default allows nothing. Call it once at startup — on the server too, before
197
+ * rendering.
198
+ *
199
+ * Inside `runInSSRContext()` it sets the policy of THAT request only, so a
200
+ * multi-tenant server can configure each render without one request's
201
+ * allowlist reaching another's. Outside a request it sets the process-wide
202
+ * policy every request without its own falls back to.
203
+ *
204
+ * A frame is judged by the policy in force when `embed()` built it: a later
205
+ * `setEmbedPolicy()` changes neither its getter `src` checks nor how SSR
206
+ * serializes it.
207
+ *
208
+ * @throws TypeError for an entry that is not an absolute `https:` URL, or
209
+ * that carries credentials, a query or a fragment.
210
+ */
211
+ declare function setEmbedPolicy(policy: EmbedPolicy): void;
212
+ /**
213
+ * The sandbox `embed()` applies by default — the tokens a hosted video player
214
+ * needs and nothing more:
215
+ *
216
+ * - `allow-scripts`, `allow-same-origin`: the player runs and keeps its own
217
+ * storage. Safe together only because the frame is never same-origin with
218
+ * the page (see `resolveEmbedSrc`).
219
+ * - `allow-presentation`: casting to a second screen.
220
+ * - `allow-popups`, `allow-popups-to-escape-sandbox`: "watch on …" links
221
+ * open a normal tab instead of a crippled sandboxed one.
222
+ *
223
+ * Absent on purpose: `allow-top-navigation*` (the player cannot navigate the
224
+ * page), `allow-forms`, `allow-modals`, `allow-downloads`,
225
+ * `allow-pointer-lock`, `allow-storage-access-by-user-activation`.
226
+ */
227
+ declare const DEFAULT_EMBED_SANDBOX: readonly string[];
228
+
229
+ /** Props for {@link embed}. */
230
+ interface EmbedProps {
231
+ /**
232
+ * The player URL. Must be absolute `https:` and match the allowlist set with
233
+ * `setEmbedPolicy()`. A getter is re-checked on every change; a refused or
234
+ * `null` value leaves the frame without a `src`.
235
+ */
236
+ src: string | (() => string | null | undefined);
237
+ /** Accessible name of the frame. Required. */
238
+ title: string | (() => string);
239
+ /**
240
+ * Permissions-policy features, as in the `allow` attribute. Narrowed to the
241
+ * player features (`accelerometer`, `autoplay`, `clipboard-write`,
242
+ * `encrypted-media`, `fullscreen`, `gyroscope`, `picture-in-picture`,
243
+ * `web-share`). Default: `"encrypted-media; picture-in-picture; fullscreen"`.
244
+ * A feature is kept only when every declaration of it grants the frame's own
245
+ * origin (no allowlist, `'src'` or `*`); `fullscreen 'none'`, `'self'` or a
246
+ * list of other origins drops it instead of granting it.
247
+ */
248
+ allow?: string;
249
+ /**
250
+ * Sandbox tokens. May only REMOVE tokens from `DEFAULT_EMBED_SANDBOX`; other
251
+ * tokens are dropped. `""` applies every restriction.
252
+ */
253
+ sandbox?: string;
254
+ /** Default `"strict-origin-when-cross-origin"`; only stricter values are accepted. */
255
+ referrerpolicy?: "no-referrer" | "same-origin" | "strict-origin" | "strict-origin-when-cross-origin";
256
+ /** Default `"lazy"`. */
257
+ loading?: "lazy" | "eager";
258
+ /** A CSS ratio such as `"16 / 9"`. Sizes the frame to the container's width. */
259
+ aspectRatio?: string;
260
+ class?: string;
261
+ id?: string;
262
+ }
263
+ /**
264
+ * Embed a third-party player (YouTube, Vimeo, …) in an `<iframe>`.
265
+ *
266
+ * The tag factories, `svgElement()` and `DOMPool` refuse `iframe`; this is the
267
+ * one runtime door, and it only opens onto origins the application allowlisted
268
+ * with `setEmbedPolicy()`:
269
+ *
270
+ * - `src` must be absolute `https:` and match an allowlisted origin and path
271
+ * prefix. A value outside the allowlist — a getter's included — is omitted
272
+ * and reported with a development warning. `javascript:`, `data:` and
273
+ * `blob:` can never match; neither can the page's own origin.
274
+ * - `title` is required. `srcdoc` is never written.
275
+ * - Safe defaults: `loading="lazy"`, `referrerpolicy=
276
+ * "strict-origin-when-cross-origin"`, `sandbox` set to
277
+ * `DEFAULT_EMBED_SANDBOX` (written before `src`, so it applies to the first
278
+ * document), and `allow` limited to player features. `sandbox`,
279
+ * `referrerpolicy` and `allow` can only be narrowed.
280
+ * - `renderToString` serializes the frame under the same policy.
281
+ *
282
+ * @example
283
+ * ```ts
284
+ * import { embed, setEmbedPolicy } from "sibujs/ui";
285
+ * setEmbedPolicy({ allowedOrigins: ["https://www.youtube-nocookie.com/embed/"] });
286
+ * embed({ src: `https://www.youtube-nocookie.com/embed/${id}`, title: "Product tour", aspectRatio: "16 / 9" });
287
+ * ```
288
+ *
289
+ * @throws TypeError when `title` is missing or blank.
290
+ */
291
+ declare function embed(props: EmbedProps): HTMLIFrameElement;
292
+
183
293
  /**
184
294
  * lazyEffect creates an effect that only activates when the target element
185
295
  * becomes visible in the viewport (via IntersectionObserver). When the element
@@ -292,4 +402,4 @@ interface ScrollLockHandle {
292
402
  */
293
403
  declare function scrollLock(): ScrollLockHandle;
294
404
 
295
- export { type DialogAriaHandle, type DialogAriaOptions, type FocusManagerHandle, type FocusManagerOptions, type FormActionHandle, type FormActionState, type FormActionSubmit, type IntervalHandle, type ListboxHandle, type ListboxOptions, type ScrollLockHandle, type TimeoutHandle, createDialogAria, createFocusManager, createListbox, formAction, hover, interval, lazyEffect, scrollLock, timeout };
405
+ export { DEFAULT_EMBED_SANDBOX, type DialogAriaHandle, type DialogAriaOptions, type EmbedPolicy, type EmbedProps, type FocusManagerHandle, type FocusManagerOptions, type FormActionHandle, type FormActionState, type FormActionSubmit, type IntervalHandle, type ListboxHandle, type ListboxOptions, type ScrollLockHandle, type TimeoutHandle, createDialogAria, createFocusManager, createListbox, embed, formAction, hover, interval, lazyEffect, scrollLock, setEmbedPolicy, timeout };