@meowdown/embed 0.1.0 → 0.2.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.
@@ -53,23 +53,6 @@ function isPromiseLike(value) {
53
53
  return typeof value === "object" && value !== null && typeof value.then === "function";
54
54
  }
55
55
 
56
- //#endregion
57
- //#region src/root.ts
58
- /**
59
- * The direct `<div data-root>` child that holds the rendered snapshot. Reused
60
- * across updates so other children the host appended survive; created on
61
- * first use.
62
- */
63
- function getRootContainer(host) {
64
- let container = host.querySelector(":scope > div[data-root]");
65
- if (!container) {
66
- container = host.ownerDocument.createElement("div");
67
- container.dataset.root = "";
68
- host.append(container);
69
- }
70
- return container;
71
- }
72
-
73
56
  //#endregion
74
57
  //#region src/safe-url.ts
75
58
  function getSafeUrl(value, protocols) {
@@ -97,4 +80,27 @@ function renderLink(content, destination) {
97
80
  }
98
81
 
99
82
  //#endregion
100
- export { useFetch as i, getSafeUrl as n, getRootContainer as r, renderLink as t };
83
+ //#region src/root.ts
84
+ /**
85
+ * The direct `<div data-root>` child that holds the rendered snapshot. Reused
86
+ * across updates so other children the host appended survive; created on
87
+ * first use.
88
+ */
89
+ function getRootContainer(host) {
90
+ let container = host.querySelector(":scope > div[data-root]");
91
+ if (!container) {
92
+ container = host.ownerDocument.createElement("div");
93
+ container.dataset.root = "";
94
+ host.append(container);
95
+ }
96
+ return container;
97
+ }
98
+
99
+ //#endregion
100
+ //#region src/is-plain-click.ts
101
+ function isPlainClick(event) {
102
+ return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;
103
+ }
104
+
105
+ //#endregion
106
+ export { useFetch as a, getSafeUrl as i, getRootContainer as n, renderLink as r, isPlainClick as t };
package/dist/x/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { i as useFetch, n as getSafeUrl, r as getRootContainer, t as renderLink } from "../render-link-BqiZxTy9.js";
1
+ import { a as useFetch, i as getSafeUrl, n as getRootContainer, r as renderLink, t as isPlainClick } from "../is-plain-click-QDpLdFP7.js";
2
2
  import { defineCustomElement, defineProps, registerCustomElement, useEffect } from "@aria-ui/core";
3
3
  import { parseXPost, parseXPostId } from "@post-embed/schema";
4
4
  import el from "crelt";
@@ -121,9 +121,6 @@ function renderError(permalink) {
121
121
  hidden: true
122
122
  }, "Media could not be loaded. ", renderLink("View on X", permalink));
123
123
  }
124
- function isPlainClick(event) {
125
- return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;
126
- }
127
124
  function renderPhoto(media, error, onClick) {
128
125
  const image = el("img", {
129
126
  src: media.url,
@@ -305,17 +302,18 @@ function useXPost(host, props) {
305
302
  const url = props.url.get();
306
303
  const value = result && !result.issues ? result.value : void 0;
307
304
  const valid = value && (props.data.get() != null || !url || parseXPostId(url) === value.id);
308
- container.replaceChildren(valid ? renderPost(value, protocols) : renderFallback(pending.get()));
305
+ container.replaceChildren(valid ? renderPost(value, protocols) : renderFallback(pending.get(), url));
309
306
  return () => {
310
307
  for (const video of container.querySelectorAll("video")) video.pause();
311
308
  };
312
309
  });
313
310
  }
314
- function renderFallback(pending) {
311
+ function renderFallback(pending, url) {
312
+ const permalink = url != null && parseXPostId(url) ? url : void 0;
315
313
  return el("article", pending ? {
316
314
  "data-fallback": "",
317
315
  "data-pending": ""
318
- } : { "data-fallback": "" }, el("header", { "data-author": "" }, el("bdi", {}, "X post")), el("p", { "data-body": "" }, pending ? "Loading this post…" : "This post is unavailable."), pending ? null : el("footer", { "data-footer": "" }, "No saved post could be displayed."));
316
+ } : { "data-fallback": "" }, el("header", { "data-author": "" }, el("bdi", {}, "X post")), el("p", { "data-body": "" }, pending ? "Loading this post…" : "This post is unavailable."), pending ? null : el("footer", { "data-footer": "" }, permalink ? renderLink("View on X", permalink) : "No saved post could be displayed."));
319
317
  }
320
318
  const XPost = defineCustomElement(useXPost, defineProps({
321
319
  data: {
@@ -1,6 +1,34 @@
1
1
  import { n as Resolver, t as FetchProps } from "../fetch-Djnul03u.js";
2
2
  import { HostElement, State } from "@aria-ui/core";
3
3
  import { YouTubeVideo } from "@post-embed/types";
4
+ //#region src/youtube/video-click.d.ts
5
+ export declare const YOUTUBE_VIDEO_CLICK = "meowdown-embed-youtube-click";
6
+ interface YouTubeVideoClickDetail {
7
+ /**
8
+ * The snapshot the card rendered.
9
+ */
10
+ video: YouTubeVideo;
11
+ videoId: string;
12
+ /**
13
+ * Where playback starts, from the `t` or `start` parameter of the URL.
14
+ */
15
+ startSeconds: number;
16
+ /**
17
+ * A Short, which plays in portrait.
18
+ */
19
+ short: boolean;
20
+ /**
21
+ * The player page for an `<iframe>`. It starts playing as soon as it loads.
22
+ */
23
+ embedUrl: string;
24
+ /**
25
+ * The rendered thumbnail: the poster `<img>`, or the poster itself when the
26
+ * snapshot has no thumbnail.
27
+ */
28
+ element: HTMLElement;
29
+ }
30
+ type YouTubeVideoClickEvent = CustomEvent<YouTubeVideoClickDetail>;
31
+ //#endregion
4
32
  //#region src/youtube/youtube-video.d.ts
5
33
  interface YouTubeVideoProps extends FetchProps<YouTubeVideo> {
6
34
  /**
@@ -21,6 +49,9 @@ declare global {
21
49
  interface HTMLElementTagNameMap {
22
50
  'meowdown-embed-youtube': YouTubeVideoElement;
23
51
  }
52
+ interface HTMLElementEventMap {
53
+ 'meowdown-embed-youtube-click': YouTubeVideoClickEvent;
54
+ }
24
55
  }
25
56
  //#endregion
26
- export type { Resolver, YouTubeVideoElement, YouTubeVideoProps };
57
+ export type { Resolver, YouTubeVideoClickDetail, YouTubeVideoClickEvent, YouTubeVideoElement, YouTubeVideoProps };
@@ -1,4 +1,4 @@
1
- import { i as useFetch, n as getSafeUrl, r as getRootContainer, t as renderLink } from "../render-link-BqiZxTy9.js";
1
+ import { a as useFetch, i as getSafeUrl, n as getRootContainer, r as renderLink, t as isPlainClick } from "../is-plain-click-QDpLdFP7.js";
2
2
  import { defineCustomElement, defineProps, registerCustomElement, useEffect } from "@aria-ui/core";
3
3
  import { parseYouTubeVideo } from "@post-embed/schema";
4
4
  import el from "crelt";
@@ -56,6 +56,22 @@ function getEmbedUrl(ref) {
56
56
  return `https://www.youtube-nocookie.com/embed/${ref.videoId}?${params}`;
57
57
  }
58
58
 
59
+ //#endregion
60
+ //#region src/youtube/video-click.ts
61
+ const YOUTUBE_VIDEO_CLICK = "meowdown-embed-youtube-click";
62
+ /**
63
+ * Returns false when a listener called `preventDefault()`: the host plays the
64
+ * video itself, so the card must not run its own default.
65
+ */
66
+ function dispatchVideoClick(target, detail) {
67
+ return target.dispatchEvent(new CustomEvent(YOUTUBE_VIDEO_CLICK, {
68
+ detail,
69
+ bubbles: true,
70
+ cancelable: true,
71
+ composed: true
72
+ }));
73
+ }
74
+
59
75
  //#endregion
60
76
  //#region src/youtube/render-video.ts
61
77
  function renderFrame(ref, title) {
@@ -75,7 +91,7 @@ function renderVideo(video, ref, playback) {
75
91
  target: "_blank",
76
92
  rel: "noopener noreferrer"
77
93
  };
78
- const posterContent = [poster ? el("img", {
94
+ const image = poster ? el("img", {
79
95
  src: poster,
80
96
  alt: "",
81
97
  width: video.thumbnail_width || void 0,
@@ -83,10 +99,19 @@ function renderVideo(video, ref, playback) {
83
99
  loading: "lazy",
84
100
  decoding: "async",
85
101
  referrerpolicy: "no-referrer"
86
- }) : void 0, el("span", {
102
+ }) : void 0;
103
+ const posterContent = [image, el("span", {
87
104
  "data-play": "",
88
105
  "aria-hidden": "true"
89
106
  })];
107
+ const onClick = (element) => {
108
+ return dispatchVideoClick(element, {
109
+ video,
110
+ ...ref,
111
+ embedUrl: getEmbedUrl(ref),
112
+ element
113
+ });
114
+ };
90
115
  let posterElement;
91
116
  if (playback === "inline") {
92
117
  const button = el("button", {
@@ -95,16 +120,23 @@ function renderVideo(video, ref, playback) {
95
120
  "aria-label": `Play: ${title}`
96
121
  }, posterContent);
97
122
  button.addEventListener("click", () => {
123
+ if (!onClick(image ?? button)) return;
98
124
  const frame = renderFrame(ref, title);
99
125
  button.replaceWith(frame);
100
126
  frame.focus();
101
127
  });
102
128
  posterElement = button;
103
- } else posterElement = el("a", {
104
- "data-poster": "",
105
- href: watchUrl,
106
- ...external
107
- }, posterContent);
129
+ } else {
130
+ const link = el("a", {
131
+ "data-poster": "",
132
+ href: watchUrl,
133
+ ...external
134
+ }, posterContent);
135
+ link.addEventListener("click", (event) => {
136
+ if (isPlainClick(event) && !onClick(image ?? link)) event.preventDefault();
137
+ });
138
+ posterElement = link;
139
+ }
108
140
  return el("article", { "data-orientation": ref.short ? "portrait" : void 0 }, posterElement, el("div", { "data-details": "" }, el("a", {
109
141
  "data-title": "",
110
142
  href: watchUrl,
@@ -161,4 +193,4 @@ function registerYouTubeVideo(name = "meowdown-embed-youtube") {
161
193
  }
162
194
 
163
195
  //#endregion
164
- export { registerYouTubeVideo, useYouTubeVideo };
196
+ export { YOUTUBE_VIDEO_CLICK, registerYouTubeVideo, useYouTubeVideo };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meowdown/embed",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",