@ulu/frontend-vue 0.5.12 → 0.5.14

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.
@@ -19,6 +19,8 @@ declare const __VLS_component: import('vue').DefineComponent<{}, {
19
19
  bodyFills: boolean;
20
20
  noBackdrop: boolean;
21
21
  noMinHeight: boolean;
22
+ autoIframe: boolean;
23
+ noPauseVideos: boolean;
22
24
  labelledby?: string | undefined;
23
25
  describedby?: string | undefined;
24
26
  title?: string | undefined;
@@ -41,6 +43,8 @@ declare const __VLS_component: import('vue').DefineComponent<{}, {
41
43
  readonly bodyFills?: boolean | undefined;
42
44
  readonly noBackdrop?: boolean | undefined;
43
45
  readonly noMinHeight?: boolean | undefined;
46
+ readonly autoIframe?: boolean | undefined;
47
+ readonly noPauseVideos?: boolean | undefined;
44
48
  readonly labelledby?: string | undefined;
45
49
  readonly describedby?: string | undefined;
46
50
  readonly title?: string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"UluModal.vue.d.ts","sourceRoot":"","sources":["../../../lib/components/collapsible/UluModal.vue"],"names":[],"mappings":"AAoEA;wBA+1BqB,uBAAuB,CAAC,OAAO,eAAe,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;;6BAEtE,CAAC,EAAE,CAAC;;;AAbjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4OAUG"}
1
+ {"version":3,"file":"UluModal.vue.d.ts","sourceRoot":"","sources":["../../../lib/components/collapsible/UluModal.vue"],"names":[],"mappings":"AAsEA;wBA07BqB,uBAAuB,CAAC,OAAO,eAAe,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;;6BAEtE,CAAC,EAAE,CAAC;;;AAbjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4OAUG"}
@@ -1,10 +1,10 @@
1
- import { useSlots as q, ref as p, computed as a, watch as B, nextTick as M, onMounted as G, onBeforeUnmount as J, createBlock as O, openBlock as c, Teleport as K, createElementVNode as d, withModifiers as Q, normalizeStyle as Z, normalizeClass as i, unref as T, createElementBlock as w, createCommentVNode as h, renderSlot as u, toDisplayString as _, createVNode as L } from "vue";
2
- import C from "../elements/UluIcon.vue.js";
3
- import { useModifiers as x } from "../../composables/useModifiers.js";
4
- import { preventScroll as ee, wasClickOutside as le } from "@ulu/utils/browser/dom.js";
5
- import { Resizer as oe, observeDialogToggle as te } from "@ulu/frontend";
6
- import { newId as se } from "../../utils/dom.js";
7
- const ne = ["aria-labelledby", "aria-describedby"], ie = ["id"], re = { class: "modal__title-text" }, ve = {
1
+ import { useSlots as J, ref as n, computed as u, watch as M, nextTick as w, onMounted as K, onBeforeUnmount as Q, createBlock as F, openBlock as d, Teleport as Z, createElementVNode as f, withModifiers as x, normalizeStyle as H, normalizeClass as r, unref as L, createElementBlock as C, createCommentVNode as g, renderSlot as m, toDisplayString as ee, createVNode as T } from "vue";
2
+ import R from "../elements/UluIcon.vue.js";
3
+ import { useModifiers as le } from "../../composables/useModifiers.js";
4
+ import { preventScroll as oe, wasClickOutside as te } from "@ulu/utils/browser/dom.js";
5
+ import { getSoleIframeLayout as se, youtubePrepVideos as ie, youtubePauseVideos as ae, Resizer as ne, observeDialogToggle as re } from "@ulu/frontend";
6
+ import { newId as ce } from "../../utils/dom.js";
7
+ const ue = ["aria-labelledby", "aria-describedby"], de = ["id"], fe = { class: "modal__title-text" }, pe = {
8
8
  __name: "UluModal",
9
9
  props: {
10
10
  /**
@@ -117,155 +117,180 @@ const ne = ["aria-labelledby", "aria-describedby"], ie = ["id"], re = { class: "
117
117
  /**
118
118
  * Modifiers (to add any modifier classes based on base class [ie. 'tertiary'])
119
119
  */
120
- modifiers: [String, Array]
120
+ modifiers: [String, Array],
121
+ /**
122
+ * Opt-in convenience behavior. If the modal body's sole content is an iframe, it automatically applies layout fixes.
123
+ */
124
+ autoIframe: Boolean,
125
+ /**
126
+ * Opt-out behavior to prevent pausing videos (YouTube and native <video>) when the modal closes.
127
+ */
128
+ noPauseVideos: Boolean
121
129
  },
122
130
  emits: ["update:modelValue", "close", "open"],
123
- setup(t, { emit: H }) {
124
- const r = H, e = t, j = q(), D = p(null), I = se("ulu-modal-title"), g = p(!1), o = p(null), R = p(null), E = a(() => e.title || j.title), f = a(() => {
131
+ setup(t, { emit: j }) {
132
+ const c = j, e = t, D = J(), N = n(null), E = ce("ulu-modal-title"), B = n(!1), o = n(null), $ = n(null), k = n(null), i = n({
133
+ isStaticSize: !1,
134
+ isFill: !1,
135
+ bodyStyle: {}
136
+ }), O = u(() => e.title || D.title), v = u(() => {
125
137
  const { allowResize: l, position: s } = e;
126
138
  if (!l || !s) return !1;
127
- const $ = ["left", "right", "center"];
128
- return $.includes(s) ? !0 : (console.warn(`Passed invalid position for resize (${s}), use ${$.join(", ")}`), !1);
129
- }), N = a(() => e.position === "center" ? "type:resizeBoth" : "type:resizeHorizontal"), P = a(() => ({
139
+ const h = ["left", "right", "center"];
140
+ return h.includes(s) ? !0 : (console.warn(`Passed invalid position for resize (${s}), use ${h.join(", ")}`), !1);
141
+ }), _ = u(() => e.position === "center" ? "type:resizeBoth" : "type:resizeHorizontal"), A = u(() => ({
130
142
  [e.position]: e.position,
131
143
  resize: e.allowResize,
132
144
  "no-resize": !e.allowResize,
133
- "no-header": !E.value,
145
+ "no-header": !O.value,
134
146
  "body-fills": e.bodyFills,
135
147
  "no-backdrop": e.noBackdrop,
136
148
  "no-min-height": e.noMinHeight,
137
149
  "non-modal": e.nonModal,
138
- "resizer-active": f.value,
150
+ "resizer-active": v.value,
139
151
  fullscreen: e.fullscreen,
140
- "fullscreen-mobile": e.fullscreenMobile
141
- })), { resolvedModifiers: F } = x({
152
+ "fullscreen-mobile": e.fullscreenMobile,
153
+ "frame-ratio": i.value.isStaticSize,
154
+ "frame-fill": i.value.isFill
155
+ })), { resolvedModifiers: U } = le({
142
156
  props: e,
143
157
  baseClass: "modal",
144
- internal: P
145
- }), U = a(() => e.labelledby ? e.labelledby : I), n = () => {
146
- r("update:modelValue", !1), r("close");
147
- }, X = () => {
148
- e.modelValue && (r("update:modelValue", !1), r("close"));
149
- }, A = (l) => {
150
- if (e.clickOutsideCloses && !g.value) {
158
+ internal: A
159
+ }), X = u(() => e.labelledby ? e.labelledby : E), a = () => {
160
+ c("update:modelValue", !1), c("close");
161
+ }, q = () => {
162
+ e.modelValue && (c("update:modelValue", !1), c("close"));
163
+ }, W = (l) => {
164
+ if (e.clickOutsideCloses && !B.value) {
151
165
  const { target: s } = l;
152
- s === o.value && le(o.value, l) && n();
166
+ s === o.value && te(o.value, l) && a();
153
167
  }
154
168
  };
155
- let m = null, v = null, b = null, y = null, z = null;
156
- const W = () => {
157
- !e.nonModal && e.preventScroll && (m = te(o.value, (l) => {
158
- l ? v = ee({ preventShift: e.preventScrollShift }) : V();
169
+ let y = null, b = null, S = null, z = null, p = null;
170
+ const Y = () => {
171
+ !e.nonModal && e.preventScroll && (y = re(o.value, (l) => {
172
+ l ? b = oe({ preventShift: e.preventScrollShift }) : P();
159
173
  }));
160
- }, Y = () => {
161
- m && (m.destroy(), m = null);
162
- }, V = () => {
163
- v && (v(), v = null);
164
- }, S = () => {
165
- if (f.value) {
174
+ }, G = () => {
175
+ y && (y.destroy(), y = null);
176
+ }, P = () => {
177
+ b && (b(), b = null);
178
+ }, I = () => {
179
+ if (v.value) {
166
180
  const l = e.position === "center" ? { fromX: "right", fromY: "bottom", multiplier: 2 } : { fromX: e.position === "right" ? "left" : "right" };
167
- b = new oe(o.value, R.value, l), y = () => {
168
- g.value = !0;
169
- }, z = () => {
181
+ S = new ne(o.value, $.value, l), z = () => {
182
+ B.value = !0;
183
+ }, p = () => {
170
184
  setTimeout(() => {
171
- g.value = !1;
185
+ B.value = !1;
172
186
  }, 0);
173
- }, o.value.addEventListener("ulu:resizer:start", y), o.value.addEventListener("ulu:resizer:end", z);
187
+ }, o.value.addEventListener("ulu:resizer:start", z), o.value.addEventListener("ulu:resizer:end", p);
174
188
  }
175
- }, k = () => {
176
- b && (b.destroy(), b = null), y && o.value && o.value.removeEventListener("ulu:resizer:start", y), z && o.value && o.value.removeEventListener("ulu:resizer:end", z);
189
+ }, V = () => {
190
+ S && (S.destroy(), S = null), z && o.value && o.value.removeEventListener("ulu:resizer:start", z), p && o.value && o.value.removeEventListener("ulu:resizer:end", p);
177
191
  };
178
- return B(() => e.modelValue, (l) => {
179
- M(() => {
180
- o.value && (l ? (o.value[e.nonModal ? "show" : "showModal"](), r("open")) : o.value.close());
192
+ return M(() => e.modelValue, (l) => {
193
+ w(() => {
194
+ if (o.value)
195
+ if (l) {
196
+ if (e.autoIframe && k.value) {
197
+ const s = se(k.value);
198
+ s && (s.iframe.classList.add("modal__frame-content"), s.isStaticSize ? (i.value.isStaticSize = !0, i.value.isFill = !1, i.value.bodyStyle = { aspectRatio: s.aspectRatio }) : (i.value.isFill = !0, i.value.isStaticSize = !1, i.value.bodyStyle = s.fillHeight ? { minHeight: s.fillHeight } : {}));
199
+ }
200
+ e.noPauseVideos || ie(o.value), o.value[e.nonModal ? "show" : "showModal"](), c("open");
201
+ } else
202
+ e.noPauseVideos || (ae(o.value), o.value.querySelectorAll("video").forEach((h) => h.pause())), o.value.close(), i.value = { isStaticSize: !1, isFill: !1, bodyStyle: {} };
181
203
  });
182
- }, { immediate: !0 }), B(f, (l) => {
183
- l ? M(() => {
184
- S();
185
- }) : k();
186
- }, { immediate: !1 }), B(() => e.position, (l, s) => {
187
- l !== s && (k(), M(() => {
188
- S();
204
+ }, { immediate: !0 }), M(v, (l) => {
205
+ l ? w(() => {
206
+ I();
207
+ }) : V();
208
+ }, { immediate: !1 }), M(() => e.position, (l, s) => {
209
+ l !== s && (V(), w(() => {
210
+ I();
189
211
  }));
190
- }), G(() => {
191
- W(), S();
192
- }), J(() => {
193
- o.value && o.value.open && o.value.close(), Y(), V(), k();
194
- }), (l, s) => (c(), O(K, {
212
+ }), K(() => {
213
+ Y(), I();
214
+ }), Q(() => {
215
+ o.value && o.value.open && o.value.close(), G(), P(), V();
216
+ }), (l, s) => (d(), F(Z, {
195
217
  to: t.teleport === !1 ? null : t.teleport,
196
218
  disabled: t.teleport === !1
197
219
  }, [
198
- d("dialog", {
199
- class: i(["modal", [T(F), t.classes.container]]),
200
- "aria-labelledby": U.value,
220
+ f("dialog", {
221
+ class: r(["modal", [L(U), t.classes.container]]),
222
+ "aria-labelledby": X.value,
201
223
  "aria-describedby": t.describedby,
202
224
  ref_key: "container",
203
225
  ref: o,
204
- style: Z({ width: D.value }),
205
- onCancel: Q(n, ["prevent"]),
206
- onClose: X,
207
- onClick: A
226
+ style: H({ width: N.value }),
227
+ onCancel: x(a, ["prevent"]),
228
+ onClose: q,
229
+ onClick: W
208
230
  }, [
209
- E.value ? (c(), w("header", {
231
+ O.value ? (d(), C("header", {
210
232
  key: 0,
211
- class: i(["modal__header", t.classes.header])
233
+ class: r(["modal__header", t.classes.header])
212
234
  }, [
213
- d("h2", {
214
- class: i(["modal__title", t.classes.title]),
215
- id: T(I)
235
+ f("h2", {
236
+ class: r(["modal__title", t.classes.title]),
237
+ id: L(E)
216
238
  }, [
217
- u(l.$slots, "title", { close: n }, () => [
218
- t.titleIcon ? (c(), O(C, {
239
+ m(l.$slots, "title", { close: a }, () => [
240
+ t.titleIcon ? (d(), F(R, {
219
241
  key: 0,
220
242
  class: "modal__title-icon",
221
243
  icon: t.titleIcon
222
- }, null, 8, ["icon"])) : h("", !0),
223
- d("span", re, _(t.title), 1)
244
+ }, null, 8, ["icon"])) : g("", !0),
245
+ f("span", fe, ee(t.title), 1)
224
246
  ])
225
- ], 10, ie),
226
- d("button", {
227
- class: i(["modal__close", t.classes.close]),
247
+ ], 10, de),
248
+ f("button", {
249
+ class: r(["modal__close", t.classes.close]),
228
250
  "aria-label": "Close modal",
229
- onClick: n,
251
+ onClick: a,
230
252
  autofocus: ""
231
253
  }, [
232
- u(l.$slots, "closeIcon", {}, () => [
233
- L(C, {
254
+ m(l.$slots, "closeIcon", {}, () => [
255
+ T(R, {
234
256
  class: "modal__close-icon",
235
257
  icon: t.closeIcon || "type:close"
236
258
  }, null, 8, ["icon"])
237
259
  ])
238
260
  ], 2)
239
- ], 2)) : h("", !0),
240
- d("div", {
241
- class: i(["modal__body", t.classes.body])
261
+ ], 2)) : g("", !0),
262
+ f("div", {
263
+ class: r(["modal__body", t.classes.body]),
264
+ style: H(i.value.bodyStyle),
265
+ ref_key: "body",
266
+ ref: k
242
267
  }, [
243
- u(l.$slots, "default", { close: n })
244
- ], 2),
245
- l.$slots.footer ? (c(), w("div", {
268
+ m(l.$slots, "default", { close: a })
269
+ ], 6),
270
+ l.$slots.footer ? (d(), C("div", {
246
271
  key: 1,
247
- class: i(["site-modal__footer", t.classes.footer])
272
+ class: r(["site-modal__footer", t.classes.footer])
248
273
  }, [
249
- u(l.$slots, "footer", { close: n })
250
- ], 2)) : h("", !0),
251
- f.value ? (c(), w("button", {
274
+ m(l.$slots, "footer", { close: a })
275
+ ], 2)) : g("", !0),
276
+ v.value ? (d(), C("button", {
252
277
  key: 2,
253
278
  class: "modal__resizer",
254
279
  ref_key: "resizer",
255
- ref: R,
280
+ ref: $,
256
281
  type: "button"
257
282
  }, [
258
- u(l.$slots, "resizerIcon", {}, () => [
259
- L(C, {
283
+ m(l.$slots, "resizerIcon", {}, () => [
284
+ T(R, {
260
285
  class: "modal__resizer-icon",
261
- icon: t.resizerIcon || N.value
286
+ icon: t.resizerIcon || _.value
262
287
  }, null, 8, ["icon"])
263
288
  ])
264
- ], 512)) : h("", !0)
265
- ], 46, ne)
289
+ ], 512)) : g("", !0)
290
+ ], 46, ue)
266
291
  ], 8, ["to", "disabled"]));
267
292
  }
268
293
  };
269
294
  export {
270
- ve as default
295
+ pe as default
271
296
  };
@@ -48,6 +48,8 @@
48
48
  <div
49
49
  class="modal__body"
50
50
  :class="classes.body"
51
+ :style="iframeState.bodyStyle"
52
+ ref="body"
51
53
  >
52
54
  <slot :close="close"/>
53
55
  </div>
@@ -72,7 +74,7 @@
72
74
  import UluIcon from "../elements/UluIcon.vue";
73
75
  import { useModifiers } from "../../composables/useModifiers.js";
74
76
  import { wasClickOutside, preventScroll as setupPreventScroll } from "@ulu/utils/browser/dom.js";
75
- import { Resizer, observeDialogToggle } from "@ulu/frontend";
77
+ import { Resizer, observeDialogToggle, getSoleIframeLayout, youtubePauseVideos, youtubePrepVideos } from "@ulu/frontend";
76
78
  import { newId } from "../../utils/dom.js";
77
79
 
78
80
  const emit = defineEmits(["update:modelValue", "close", "open"]);
@@ -188,6 +190,14 @@
188
190
  * Modifiers (to add any modifier classes based on base class [ie. 'tertiary'])
189
191
  */
190
192
  modifiers: [String, Array],
193
+ /**
194
+ * Opt-in convenience behavior. If the modal body's sole content is an iframe, it automatically applies layout fixes.
195
+ */
196
+ autoIframe: Boolean,
197
+ /**
198
+ * Opt-out behavior to prevent pausing videos (YouTube and native <video>) when the modal closes.
199
+ */
200
+ noPauseVideos: Boolean,
191
201
  });
192
202
 
193
203
  const slots = useSlots();
@@ -198,6 +208,13 @@
198
208
 
199
209
  const container = ref(null);
200
210
  const resizer = ref(null);
211
+ const body = ref(null);
212
+
213
+ const iframeState = ref({
214
+ isStaticSize: false,
215
+ isFill: false,
216
+ bodyStyle: {}
217
+ });
201
218
 
202
219
  const hasHeader = computed(() => props.title || slots.title);
203
220
 
@@ -230,6 +247,8 @@
230
247
  "resizer-active": resizerEnabled.value,
231
248
  "fullscreen": props.fullscreen,
232
249
  "fullscreen-mobile": props.fullscreenMobile,
250
+ "frame-ratio": iframeState.value.isStaticSize,
251
+ "frame-fill": iframeState.value.isFill,
233
252
  }));
234
253
 
235
254
  const { resolvedModifiers } = useModifiers({
@@ -329,10 +348,34 @@
329
348
  nextTick(() => {
330
349
  if (container.value) {
331
350
  if (newValue) {
351
+ if (props.autoIframe && body.value) {
352
+ const layout = getSoleIframeLayout(body.value);
353
+ if (layout) {
354
+ layout.iframe.classList.add("modal__frame-content");
355
+ if (layout.isStaticSize) {
356
+ iframeState.value.isStaticSize = true;
357
+ iframeState.value.isFill = false;
358
+ iframeState.value.bodyStyle = { aspectRatio: layout.aspectRatio };
359
+ } else {
360
+ iframeState.value.isFill = true;
361
+ iframeState.value.isStaticSize = false;
362
+ iframeState.value.bodyStyle = layout.fillHeight ? { minHeight: layout.fillHeight } : {};
363
+ }
364
+ }
365
+ }
366
+ if (!props.noPauseVideos) {
367
+ youtubePrepVideos(container.value);
368
+ }
332
369
  container.value[props.nonModal ? "show" : "showModal"]();
333
370
  emit("open");
334
371
  } else {
372
+ if (!props.noPauseVideos) {
373
+ youtubePauseVideos(container.value);
374
+ const nativeVideos = container.value.querySelectorAll("video");
375
+ nativeVideos.forEach(video => video.pause());
376
+ }
335
377
  container.value.close();
378
+ iframeState.value = { isStaticSize: false, isFill: false, bodyStyle: {} };
336
379
  }
337
380
  }
338
381
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulu/frontend-vue",
3
- "version": "0.5.12",
3
+ "version": "0.5.14",
4
4
  "description": "A modular, tree-shakeable Vue 3 component library for the Ulu Frontend theming system, plus general utilities for Vue development",
5
5
  "type": "module",
6
6
  "files": [
@@ -65,7 +65,7 @@
65
65
  "@fortawesome/vue-fontawesome": "^3.0.8",
66
66
  "@headlessui/vue": "^1.7.23",
67
67
  "@portabletext/vue": "^1.0.14",
68
- "@ulu/frontend": "^0.4.9",
68
+ "@ulu/frontend": "^0.4.11",
69
69
  "@ulu/utils": "^0.0.34",
70
70
  "@unhead/vue": "^2.0.11",
71
71
  "fuse.js": "^6.6.2",
@@ -87,7 +87,7 @@
87
87
  "@storybook/addon-essentials": "^9.0.0-alpha.12",
88
88
  "@storybook/addon-links": "^9.1.1",
89
89
  "@storybook/vue3-vite": "^9.1.1",
90
- "@ulu/frontend": "^0.4.6",
90
+ "@ulu/frontend": "^0.4.11",
91
91
  "@ulu/utils": "^0.0.34",
92
92
  "@unhead/vue": "^2.0.11",
93
93
  "@vitejs/plugin-vue": "^6.0.0",