@editframe/react 0.7.0-beta.9 → 0.8.0-beta.2

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.
@@ -0,0 +1,89 @@
1
+ import { default as React } from 'react';
2
+
3
+ type DistributiveOmit<T, K extends string | number | symbol> = T extends any ? K extends keyof T ? Omit<T, K> : T : T;
4
+ type PropsWithoutRef<T> = DistributiveOmit<T, "ref">;
5
+ /**
6
+ * Creates a type to be used for the props of a web component used directly in
7
+ * React JSX.
8
+ *
9
+ * Example:
10
+ *
11
+ * ```ts
12
+ * declare module "react" {
13
+ * namespace JSX {
14
+ * interface IntrinsicElements {
15
+ * 'x-foo': WebComponentProps<XFoo>;
16
+ * }
17
+ * }
18
+ * }
19
+ * ```
20
+ */
21
+ export type WebComponentProps<I extends HTMLElement> = React.DetailedHTMLProps<React.HTMLAttributes<I>, I> & ElementProps<I>;
22
+ /**
23
+ * Type of the React component wrapping the web component. This is the return
24
+ * type of `createComponent`.
25
+ */
26
+ export type ReactWebComponent<I extends HTMLElement, E extends EventNames = {}> = React.ForwardRefExoticComponent<PropsWithoutRef<ComponentProps<I, E>> & React.RefAttributes<I>>;
27
+ type ElementProps<I> = Partial<Omit<I, keyof HTMLElement>>;
28
+ type ComponentProps<I, E extends EventNames = {}> = Omit<React.HTMLAttributes<I>, keyof E | keyof ElementProps<I>> & EventListeners<E> & ElementProps<I>;
29
+ /**
30
+ * Type used to cast an event name with an event type when providing the
31
+ * `events` option to `createComponent` for better typing of the event handler
32
+ * prop.
33
+ *
34
+ * Example:
35
+ *
36
+ * ```ts
37
+ * const FooComponent = createComponent({
38
+ * ...
39
+ * events: {
40
+ * onfoo: 'foo' as EventName<FooEvent>,
41
+ * }
42
+ * });
43
+ * ```
44
+ *
45
+ * `onfoo` prop will have the type `(e: FooEvent) => void`.
46
+ */
47
+ export type EventName<T extends Event = Event> = string & {
48
+ __eventType: T;
49
+ };
50
+ type EventNames = Record<string, EventName | string>;
51
+ type EventListeners<R extends EventNames> = {
52
+ [K in keyof R]?: R[K] extends EventName ? (e: R[K]["__eventType"]) => void : (e: Event) => void;
53
+ };
54
+ export interface Options<I extends HTMLElement, E extends EventNames = {}> {
55
+ react: typeof React;
56
+ tagName: string;
57
+ elementClass: Constructor<I>;
58
+ events?: E;
59
+ displayName?: string;
60
+ }
61
+ type Constructor<T> = {
62
+ new (): T;
63
+ };
64
+ /**
65
+ * Creates a React component for a custom element. Properties are distinguished
66
+ * from attributes automatically, and events can be configured so they are added
67
+ * to the custom element as event listeners.
68
+ *
69
+ * @param options An options bag containing the parameters needed to generate a
70
+ * wrapped web component.
71
+ *
72
+ * @param options.react The React module, typically imported from the `react`
73
+ * npm package.
74
+ * @param options.tagName The custom element tag name registered via
75
+ * `customElements.define`.
76
+ * @param options.elementClass The custom element class registered via
77
+ * `customElements.define`.
78
+ * @param options.events An object listing events to which the component can
79
+ * listen. The object keys are the event property names passed in via React
80
+ * props and the object values are the names of the corresponding events
81
+ * generated by the custom element. For example, given `{onactivate:
82
+ * 'activate'}` an event function may be passed via the component's `onactivate`
83
+ * prop and will be called when the custom element fires its `activate` event.
84
+ * @param options.displayName A React component display name, used in debugging
85
+ * messages. Default value is inferred from the name of custom element class
86
+ * registered via `customElements.define`.
87
+ */
88
+ export declare const createComponent: <I extends HTMLElement, E extends EventNames = {}>({ react: React, tagName, elementClass, events, displayName, }: Options<I, E>) => ReactWebComponent<I, E>;
89
+ export {};
@@ -0,0 +1,128 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2018 Google LLC
4
+ * SPDX-License-Identifier: BSD-3-Clause
5
+ */
6
+ const reservedReactProperties = /* @__PURE__ */ new Set([
7
+ "id",
8
+ "children",
9
+ "localName",
10
+ "ref",
11
+ "style",
12
+ "className"
13
+ ]);
14
+ const listenedEvents = /* @__PURE__ */ new WeakMap();
15
+ const addOrUpdateEventListener = (node, event, listener) => {
16
+ let events = listenedEvents.get(node);
17
+ if (events === void 0) {
18
+ listenedEvents.set(node, events = /* @__PURE__ */ new Map());
19
+ }
20
+ let handler = events.get(event);
21
+ if (listener !== void 0) {
22
+ if (handler === void 0) {
23
+ events.set(event, handler = { handleEvent: listener });
24
+ node.addEventListener(event, handler);
25
+ } else {
26
+ handler.handleEvent = listener;
27
+ }
28
+ } else if (handler !== void 0) {
29
+ events.delete(event);
30
+ node.removeEventListener(event, handler);
31
+ }
32
+ };
33
+ const setProperty = (node, name, value, old, events) => {
34
+ const event = events?.[name];
35
+ if (event !== void 0) {
36
+ if (value !== old) {
37
+ addOrUpdateEventListener(node, event, value);
38
+ }
39
+ return;
40
+ }
41
+ node[name] = value;
42
+ if ((value === void 0 || value === null) && name in HTMLElement.prototype) {
43
+ node.removeAttribute(name);
44
+ }
45
+ };
46
+ const createComponent = ({
47
+ react: React,
48
+ tagName,
49
+ elementClass,
50
+ events,
51
+ displayName
52
+ }) => {
53
+ const eventProps = new Set(Object.keys(events ?? {}));
54
+ {
55
+ for (const p of reservedReactProperties) {
56
+ if (p in elementClass.prototype && !(p in HTMLElement.prototype)) {
57
+ console.warn(
58
+ `${tagName} contains property ${p} which is a React reserved property. It will be used by React and not set on the element.`
59
+ );
60
+ }
61
+ }
62
+ }
63
+ const ReactComponent = React.forwardRef((props, ref) => {
64
+ const prevElemPropsRef = React.useRef(/* @__PURE__ */ new Map());
65
+ const elementRef = React.useRef(null);
66
+ const reactProps = {};
67
+ const elementProps = {};
68
+ for (const [k, v] of Object.entries(props)) {
69
+ if (reservedReactProperties.has(k)) {
70
+ reactProps[k === "className" ? "class" : k] = v;
71
+ continue;
72
+ }
73
+ if (eventProps.has(k) || k in elementClass.prototype) {
74
+ elementProps[k] = v;
75
+ continue;
76
+ }
77
+ reactProps[k] = v;
78
+ }
79
+ {
80
+ React.useLayoutEffect(() => {
81
+ if (elementRef.current === null) {
82
+ return;
83
+ }
84
+ const newElemProps = /* @__PURE__ */ new Map();
85
+ for (const key in elementProps) {
86
+ setProperty(
87
+ elementRef.current,
88
+ key,
89
+ props[key],
90
+ prevElemPropsRef.current.get(key),
91
+ events
92
+ );
93
+ prevElemPropsRef.current.delete(key);
94
+ newElemProps.set(key, props[key]);
95
+ }
96
+ for (const [key, value] of prevElemPropsRef.current) {
97
+ setProperty(elementRef.current, key, void 0, value, events);
98
+ }
99
+ prevElemPropsRef.current = newElemProps;
100
+ });
101
+ React.useLayoutEffect(() => {
102
+ elementRef.current?.removeAttribute("defer-hydration");
103
+ }, []);
104
+ }
105
+ {
106
+ reactProps.suppressHydrationWarning = true;
107
+ }
108
+ return React.createElement(tagName, {
109
+ ...reactProps,
110
+ ref: React.useCallback(
111
+ (node) => {
112
+ elementRef.current = node;
113
+ if (typeof ref === "function") {
114
+ ref(node);
115
+ } else if (ref !== null) {
116
+ ref.current = node;
117
+ }
118
+ },
119
+ [ref]
120
+ )
121
+ });
122
+ });
123
+ ReactComponent.displayName = displayName ?? elementClass.name;
124
+ return ReactComponent;
125
+ };
126
+ export {
127
+ createComponent
128
+ };
@@ -0,0 +1,3 @@
1
+ import { EFAudio as EFAudioElement } from '../../../elements/src';
2
+
3
+ export declare const EFAudio: import('@lit/react').ReactWebComponent<EFAudioElement, {}>;
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { createComponent } from "@lit/react";
3
+ import { EFAudio as EFAudio$1 } from "@editframe/elements";
4
+ const EFAudio = createComponent({
5
+ tagName: "ef-audio",
6
+ elementClass: EFAudio$1,
7
+ react: React
8
+ });
9
+ export {
10
+ EFAudio
11
+ };
@@ -0,0 +1,4 @@
1
+ import { EFCaptions as EFCaptionsElement, EFCaptionsActiveWord as EFCaptionsActiveWordElement } from '../../../elements/src';
2
+
3
+ export declare const EFCaptions: import('@lit/react').ReactWebComponent<EFCaptionsElement, {}>;
4
+ export declare const EFCaptionsActiveWord: import('@lit/react').ReactWebComponent<EFCaptionsActiveWordElement, {}>;
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ import { createComponent } from "@lit/react";
3
+ import { EFCaptions as EFCaptions$1, EFCaptionsActiveWord as EFCaptionsActiveWord$1 } from "@editframe/elements";
4
+ const EFCaptions = createComponent({
5
+ tagName: "ef-captions",
6
+ elementClass: EFCaptions$1,
7
+ react: React
8
+ });
9
+ const EFCaptionsActiveWord = createComponent({
10
+ tagName: "ef-captions-active-word",
11
+ elementClass: EFCaptionsActiveWord$1,
12
+ react: React
13
+ });
14
+ export {
15
+ EFCaptions,
16
+ EFCaptionsActiveWord
17
+ };
@@ -0,0 +1,3 @@
1
+ import { EFImage as EFImageElement } from '../../../elements/src';
2
+
3
+ export declare const EFImage: import('@lit/react').ReactWebComponent<EFImageElement, {}>;
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { createComponent } from "@lit/react";
3
+ import { EFImage as EFImage$1 } from "@editframe/elements";
4
+ const EFImage = createComponent({
5
+ tagName: "ef-image",
6
+ elementClass: EFImage$1,
7
+ react: React
8
+ });
9
+ export {
10
+ EFImage
11
+ };
@@ -0,0 +1,3 @@
1
+ import { EFTimegroup as EFTimegroupElement } from '../../../elements/src';
2
+
3
+ export declare const EFTimegroup: import('../create-component.ts').ReactWebComponent<EFTimegroupElement, {}>;
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { createComponent } from "../create-component.js";
3
+ import { EFTimegroup as EFTimegroup$1 } from "@editframe/elements";
4
+ const EFTimegroup = createComponent({
5
+ tagName: "ef-timegroup",
6
+ elementClass: EFTimegroup$1,
7
+ react: React
8
+ });
9
+ export {
10
+ EFTimegroup
11
+ };
@@ -0,0 +1,3 @@
1
+ import { EFVideo as EFVideoElement } from '../../../elements/src';
2
+
3
+ export declare const EFVideo: import('@lit/react').ReactWebComponent<EFVideoElement, {}>;
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { createComponent } from "@lit/react";
3
+ import { EFVideo as EFVideo$1 } from "@editframe/elements";
4
+ const EFVideo = createComponent({
5
+ tagName: "ef-video",
6
+ elementClass: EFVideo$1,
7
+ react: React
8
+ });
9
+ export {
10
+ EFVideo
11
+ };
@@ -0,0 +1,3 @@
1
+ import { EFWaveform as EFWaveformElement } from '../../../elements/src';
2
+
3
+ export declare const EFWaveform: import('@lit/react').ReactWebComponent<EFWaveformElement, {}>;
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { createComponent } from "@lit/react";
3
+ import { EFWaveform as EFWaveform$1 } from "@editframe/elements";
4
+ const EFWaveform = createComponent({
5
+ tagName: "ef-waveform",
6
+ elementClass: EFWaveform$1,
7
+ react: React
8
+ });
9
+ export {
10
+ EFWaveform
11
+ };
@@ -0,0 +1,3 @@
1
+ import { EFFilmstrip as EFFilmstripElement } from '../../../elements/src';
2
+
3
+ export declare const EFFilmstrip: import('@lit/react').ReactWebComponent<EFFilmstripElement, {}>;
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { createComponent } from "@lit/react";
3
+ import { EFFilmstrip as EFFilmstrip$1 } from "@editframe/elements";
4
+ const EFFilmstrip = createComponent({
5
+ tagName: "ef-filmstrip",
6
+ elementClass: EFFilmstrip$1,
7
+ react: React
8
+ });
9
+ export {
10
+ EFFilmstrip
11
+ };
@@ -0,0 +1,3 @@
1
+ import { EFPreview as EFPreviewElement } from '../../../elements/src';
2
+
3
+ export declare const EFPreview: import('@lit/react').ReactWebComponent<EFPreviewElement, {}>;
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { createComponent } from "@lit/react";
3
+ import { EFPreview as EFPreview$1 } from "@editframe/elements";
4
+ const EFPreview = createComponent({
5
+ tagName: "ef-preview",
6
+ elementClass: EFPreview$1,
7
+ react: React
8
+ });
9
+ export {
10
+ EFPreview
11
+ };
@@ -0,0 +1,3 @@
1
+ import { EFWorkbench as EFWorkbenchElement } from '../../../elements/src';
2
+
3
+ export declare const EFWorkbench: import('@lit/react').ReactWebComponent<EFWorkbenchElement, {}>;
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { createComponent } from "@lit/react";
3
+ import { EFWorkbench as EFWorkbench$1 } from "@editframe/elements";
4
+ const EFWorkbench = createComponent({
5
+ tagName: "ef-workbench",
6
+ elementClass: EFWorkbench$1,
7
+ react: React
8
+ });
9
+ export {
10
+ EFWorkbench
11
+ };
@@ -0,0 +1,8 @@
1
+ import { EFTimegroup } from '../../../elements/src';
2
+
3
+ export declare const useTimingInfo: (timegroupRef?: React.RefObject<EFTimegroup>) => {
4
+ ref: import('react').RefObject<EFTimegroup>;
5
+ ownCurrentTimeMs: number;
6
+ durationMs: number;
7
+ percentComplete: number;
8
+ };
@@ -0,0 +1,35 @@
1
+ import { useState, useEffect, useRef } from "react";
2
+ class CurrentTimeController {
3
+ constructor(host, setCurrentTime) {
4
+ this.host = host;
5
+ this.setCurrentTime = setCurrentTime;
6
+ this.host.addController(this);
7
+ }
8
+ hostDisconnected() {
9
+ this.host.removeController(this);
10
+ }
11
+ hostUpdated() {
12
+ this.setCurrentTime({
13
+ ownCurrentTimeMs: this.host.ownCurrentTimeMs,
14
+ durationMs: this.host.durationMs,
15
+ percentComplete: this.host.ownCurrentTimeMs / this.host.durationMs
16
+ });
17
+ }
18
+ }
19
+ const useTimingInfo = (timegroupRef = useRef(null)) => {
20
+ const [timeInfo, setTimeInfo] = useState({
21
+ ownCurrentTimeMs: 0,
22
+ durationMs: 0,
23
+ percentComplete: 0
24
+ });
25
+ useEffect(() => {
26
+ if (!timegroupRef.current) {
27
+ throw new Error("Timegroup ref not set");
28
+ }
29
+ new CurrentTimeController(timegroupRef.current, setTimeInfo);
30
+ }, [timegroupRef.current]);
31
+ return { ...timeInfo, ref: timegroupRef };
32
+ };
33
+ export {
34
+ useTimingInfo
35
+ };
@@ -0,0 +1,10 @@
1
+ export { EFAudio } from './elements/EFAudio.ts';
2
+ export { EFCaptions, EFCaptionsActiveWord } from './elements/EFCaptions.ts';
3
+ export { EFImage } from './elements/EFImage.ts';
4
+ export { EFTimegroup } from './elements/EFTimegroup.ts';
5
+ export { EFVideo } from './elements/EFVideo.ts';
6
+ export { EFWaveform } from './elements/EFWaveform.ts';
7
+ export { EFWorkbench } from './gui/EFWorkbench.ts';
8
+ export { EFFilmstrip } from './gui/EFFilmstrip.ts';
9
+ export { EFPreview } from './gui/EFPreview.ts';
10
+ export { useTimingInfo } from './hooks/useTimingInfo.ts';
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ import { EFAudio } from "./elements/EFAudio.js";
2
+ import { EFCaptions, EFCaptionsActiveWord } from "./elements/EFCaptions.js";
3
+ import { EFImage } from "./elements/EFImage.js";
4
+ import { EFTimegroup } from "./elements/EFTimegroup.js";
5
+ import { EFVideo } from "./elements/EFVideo.js";
6
+ import { EFWaveform } from "./elements/EFWaveform.js";
7
+ import { EFWorkbench } from "./gui/EFWorkbench.js";
8
+ import { EFFilmstrip } from "./gui/EFFilmstrip.js";
9
+ import { EFPreview } from "./gui/EFPreview.js";
10
+ import { useTimingInfo } from "./hooks/useTimingInfo.js";
11
+ export {
12
+ EFAudio,
13
+ EFCaptions,
14
+ EFCaptionsActiveWord,
15
+ EFFilmstrip,
16
+ EFImage,
17
+ EFPreview,
18
+ EFTimegroup,
19
+ EFVideo,
20
+ EFWaveform,
21
+ EFWorkbench,
22
+ useTimingInfo
23
+ };
package/package.json CHANGED
@@ -1,16 +1,12 @@
1
1
  {
2
2
  "name": "@editframe/react",
3
- "version": "0.7.0-beta.9",
3
+ "version": "0.8.0-beta.2",
4
4
  "description": "",
5
5
  "exports": {
6
6
  ".": {
7
7
  "import": {
8
- "default": "./dist/index.js",
9
- "types": "./dist/packages/react/src/index.d.ts"
10
- },
11
- "require": {
12
- "default": "./dist/index.cjs",
13
- "types": "./dist/packages/react/src/index.d.ts"
8
+ "types": "./dist/packages/react/src/index.d.ts",
9
+ "default": "./dist/index.js"
14
10
  }
15
11
  }
16
12
  },
@@ -23,16 +19,18 @@
23
19
  "author": "",
24
20
  "license": "UNLICENSED",
25
21
  "dependencies": {
26
- "@editframe/elements": "0.7.0-beta.9",
22
+ "@editframe/elements": "0.8.0-beta.2",
27
23
  "@lit/react": "^1.0.5",
28
24
  "debug": "^4.3.5",
29
25
  "react": "^18.3.0",
30
26
  "react-dom": "^18.3.0"
31
27
  },
32
28
  "devDependencies": {
29
+ "@types/node": "^22.0.0",
33
30
  "@types/react": "^18.3.0",
34
31
  "@types/react-dom": "^18.3.0",
35
32
  "rollup-plugin-tsconfig-paths": "^1.5.2",
33
+ "typescript": "^5.5.4",
36
34
  "vite-plugin-dts": "^3.9.1",
37
35
  "vite-tsconfig-paths": "^4.3.2"
38
36
  }
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { createComponent } from "../create-component";
2
+ import { createComponent } from "../create-component.ts";
3
3
  import { EFTimegroup as EFTimegroupElement } from "@editframe/elements";
4
4
 
5
5
  export const EFTimegroup = createComponent({
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import { createComponent } from "@lit/react";
3
+ import { EFPreview as EFPreviewElement } from "@editframe/elements";
4
+
5
+ export const EFPreview = createComponent({
6
+ tagName: "ef-preview",
7
+ elementClass: EFPreviewElement,
8
+ react: React,
9
+ });
package/CHANGELOG.md DELETED
@@ -1,8 +0,0 @@
1
- # @editframe/react
2
-
3
- ## 0.7.0-beta.9
4
-
5
- ### Patch Changes
6
-
7
- - Updated dependencies [[`17c4452`](https://github.com/editframe/elements/commit/17c4452f679b042ac0accd9bf520728455e8cc2c)]:
8
- - @editframe/elements@0.7.0-beta.9
@@ -1,6 +0,0 @@
1
- const path = require("node:path");
2
- module.exports = {
3
- plugins: {
4
- tailwindcss: {},
5
- },
6
- };
@@ -1,347 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2018 Google LLC
4
- * SPDX-License-Identifier: BSD-3-Clause
5
- */
6
-
7
- import type React from "react";
8
-
9
- const NODE_MODE = false;
10
- const DEV_MODE = true;
11
-
12
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
- type DistributiveOmit<T, K extends string | number | symbol> = T extends any
14
- ? K extends keyof T
15
- ? Omit<T, K>
16
- : T
17
- : T;
18
- type PropsWithoutRef<T> = DistributiveOmit<T, "ref">;
19
-
20
- /**
21
- * Creates a type to be used for the props of a web component used directly in
22
- * React JSX.
23
- *
24
- * Example:
25
- *
26
- * ```ts
27
- * declare module "react" {
28
- * namespace JSX {
29
- * interface IntrinsicElements {
30
- * 'x-foo': WebComponentProps<XFoo>;
31
- * }
32
- * }
33
- * }
34
- * ```
35
- */
36
- export type WebComponentProps<I extends HTMLElement> = React.DetailedHTMLProps<
37
- React.HTMLAttributes<I>,
38
- I
39
- > &
40
- ElementProps<I>;
41
-
42
- /**
43
- * Type of the React component wrapping the web component. This is the return
44
- * type of `createComponent`.
45
- */
46
- export type ReactWebComponent<
47
- I extends HTMLElement,
48
- E extends EventNames = {},
49
- > = React.ForwardRefExoticComponent<
50
- // TODO(augustjk): Remove and use `React.PropsWithoutRef` when
51
- // https://github.com/preactjs/preact/issues/4124 is fixed.
52
- PropsWithoutRef<ComponentProps<I, E>> & React.RefAttributes<I>
53
- >;
54
-
55
- // Props derived from custom element class. Currently has limitations of making
56
- // all properties optional and also surfaces life cycle methods in autocomplete.
57
- // TODO(augustjk) Consider omitting keyof LitElement to remove "internal"
58
- // lifecycle methods or allow user to explicitly provide props.
59
- type ElementProps<I> = Partial<Omit<I, keyof HTMLElement>>;
60
-
61
- // Acceptable props to the React component.
62
- type ComponentProps<I, E extends EventNames = {}> = Omit<
63
- React.HTMLAttributes<I>,
64
- // Prefer type of provided event handler props or those on element over
65
- // built-in HTMLAttributes
66
- keyof E | keyof ElementProps<I>
67
- > &
68
- EventListeners<E> &
69
- ElementProps<I>;
70
-
71
- /**
72
- * Type used to cast an event name with an event type when providing the
73
- * `events` option to `createComponent` for better typing of the event handler
74
- * prop.
75
- *
76
- * Example:
77
- *
78
- * ```ts
79
- * const FooComponent = createComponent({
80
- * ...
81
- * events: {
82
- * onfoo: 'foo' as EventName<FooEvent>,
83
- * }
84
- * });
85
- * ```
86
- *
87
- * `onfoo` prop will have the type `(e: FooEvent) => void`.
88
- */
89
- export type EventName<T extends Event = Event> = string & {
90
- __eventType: T;
91
- };
92
-
93
- // A key value map matching React prop names to event names.
94
- type EventNames = Record<string, EventName | string>;
95
-
96
- // A map of expected event listener types based on EventNames.
97
- type EventListeners<R extends EventNames> = {
98
- [K in keyof R]?: R[K] extends EventName
99
- ? (e: R[K]["__eventType"]) => void
100
- : (e: Event) => void;
101
- };
102
-
103
- export interface Options<I extends HTMLElement, E extends EventNames = {}> {
104
- react: typeof React;
105
- tagName: string;
106
- elementClass: Constructor<I>;
107
- events?: E;
108
- displayName?: string;
109
- }
110
-
111
- type Constructor<T> = { new (): T };
112
-
113
- const reservedReactProperties = new Set([
114
- "id",
115
- "children",
116
- "localName",
117
- "ref",
118
- "style",
119
- "className",
120
- ]);
121
-
122
- const listenedEvents = new WeakMap<Element, Map<string, EventListenerObject>>();
123
-
124
- /**
125
- * Adds an event listener for the specified event to the given node. In the
126
- * React setup, there should only ever be one event listener. Thus, for
127
- * efficiency only one listener is added and the handler for that listener is
128
- * updated to point to the given listener function.
129
- */
130
- const addOrUpdateEventListener = (
131
- node: Element,
132
- event: string,
133
- listener: (event?: Event) => void,
134
- ) => {
135
- let events = listenedEvents.get(node);
136
- if (events === undefined) {
137
- // biome-ignore lint/suspicious/noAssignInExpressions: copied from @lit
138
- listenedEvents.set(node, (events = new Map()));
139
- }
140
- let handler = events.get(event);
141
- if (listener !== undefined) {
142
- // If necessary, add listener and track handler
143
- if (handler === undefined) {
144
- // biome-ignore lint/suspicious/noAssignInExpressions: copied from @lit
145
- events.set(event, (handler = { handleEvent: listener }));
146
- node.addEventListener(event, handler);
147
- // Otherwise just update the listener with new value
148
- } else {
149
- handler.handleEvent = listener;
150
- }
151
- // Remove listener if one exists and value is undefined
152
- } else if (handler !== undefined) {
153
- events.delete(event);
154
- node.removeEventListener(event, handler);
155
- }
156
- };
157
-
158
- /**
159
- * Sets properties and events on custom elements. These properties and events
160
- * have been pre-filtered so we know they should apply to the custom element.
161
- */
162
- const setProperty = <E extends Element>(
163
- node: E,
164
- name: string,
165
- value: unknown,
166
- old: unknown,
167
- events?: EventNames,
168
- ) => {
169
- const event = events?.[name];
170
- // Dirty check event value.
171
- if (event !== undefined) {
172
- if (value !== old) {
173
- addOrUpdateEventListener(node, event, value as (e?: Event) => void);
174
- }
175
- return;
176
- }
177
- // But don't dirty check properties; elements are assumed to do this.
178
- node[name as keyof E] = value as E[keyof E];
179
-
180
- // This block is to replicate React's behavior for attributes of native
181
- // elements where `undefined` or `null` values result in attributes being
182
- // removed.
183
- // https://github.com/facebook/react/blob/899cb95f52cc83ab5ca1eb1e268c909d3f0961e7/packages/react-dom-bindings/src/client/DOMPropertyOperations.js#L107-L141
184
- //
185
- // It's only needed here for native HTMLElement properties that reflect
186
- // attributes of the same name but don't have that behavior like "id" or
187
- // "draggable".
188
- if (
189
- (value === undefined || value === null) &&
190
- name in HTMLElement.prototype
191
- ) {
192
- node.removeAttribute(name);
193
- }
194
- };
195
-
196
- /**
197
- * Creates a React component for a custom element. Properties are distinguished
198
- * from attributes automatically, and events can be configured so they are added
199
- * to the custom element as event listeners.
200
- *
201
- * @param options An options bag containing the parameters needed to generate a
202
- * wrapped web component.
203
- *
204
- * @param options.react The React module, typically imported from the `react`
205
- * npm package.
206
- * @param options.tagName The custom element tag name registered via
207
- * `customElements.define`.
208
- * @param options.elementClass The custom element class registered via
209
- * `customElements.define`.
210
- * @param options.events An object listing events to which the component can
211
- * listen. The object keys are the event property names passed in via React
212
- * props and the object values are the names of the corresponding events
213
- * generated by the custom element. For example, given `{onactivate:
214
- * 'activate'}` an event function may be passed via the component's `onactivate`
215
- * prop and will be called when the custom element fires its `activate` event.
216
- * @param options.displayName A React component display name, used in debugging
217
- * messages. Default value is inferred from the name of custom element class
218
- * registered via `customElements.define`.
219
- */
220
- export const createComponent = <
221
- I extends HTMLElement,
222
- E extends EventNames = {},
223
- >({
224
- react: React,
225
- tagName,
226
- elementClass,
227
- events,
228
- displayName,
229
- }: Options<I, E>): ReactWebComponent<I, E> => {
230
- const eventProps = new Set(Object.keys(events ?? {}));
231
-
232
- if (DEV_MODE) {
233
- for (const p of reservedReactProperties) {
234
- if (p in elementClass.prototype && !(p in HTMLElement.prototype)) {
235
- // Note, this effectively warns only for `ref` since the other
236
- // reserved props are on HTMLElement.prototype. To address this
237
- // would require crawling down the prototype, which doesn't feel worth
238
- // it since implementing these properties on an element is extremely
239
- // rare.
240
- console.warn(
241
- `${tagName} contains property ${p} which is a React reserved property. It will be used by React and not set on the element.`,
242
- );
243
- }
244
- }
245
- }
246
-
247
- type Props = ComponentProps<I, E>;
248
-
249
- const ReactComponent = React.forwardRef<I, Props>((props, ref) => {
250
- const prevElemPropsRef = React.useRef(new Map());
251
- const elementRef = React.useRef<I | null>(null);
252
-
253
- // Props to be passed to React.createElement
254
- const reactProps: Record<string, unknown> = {};
255
- // Props to be set on element with setProperty
256
- const elementProps: Record<string, unknown> = {};
257
-
258
- for (const [k, v] of Object.entries(props)) {
259
- if (reservedReactProperties.has(k)) {
260
- // React does *not* handle `className` for custom elements so
261
- // coerce it to `class` so it's handled correctly.
262
- reactProps[k === "className" ? "class" : k] = v;
263
- continue;
264
- }
265
-
266
- if (eventProps.has(k) || k in elementClass.prototype) {
267
- elementProps[k] = v;
268
- continue;
269
- }
270
-
271
- reactProps[k] = v;
272
- }
273
-
274
- // useLayoutEffect produces warnings during server rendering.
275
- if (!NODE_MODE) {
276
- // This one has no dependency array so it'll run on every re-render.
277
- React.useLayoutEffect(() => {
278
- if (elementRef.current === null) {
279
- return;
280
- }
281
- const newElemProps = new Map();
282
- for (const key in elementProps) {
283
- setProperty(
284
- elementRef.current,
285
- key,
286
- props[key],
287
- prevElemPropsRef.current.get(key),
288
- events,
289
- );
290
- prevElemPropsRef.current.delete(key);
291
- newElemProps.set(key, props[key]);
292
- }
293
- // "Unset" any props from previous render that no longer exist.
294
- // Setting to `undefined` seems like the correct thing to "unset"
295
- // but currently React will set it as `null`.
296
- // See https://github.com/facebook/react/issues/28203
297
- for (const [key, value] of prevElemPropsRef.current) {
298
- setProperty(elementRef.current, key, undefined, value, events);
299
- }
300
- prevElemPropsRef.current = newElemProps;
301
- });
302
-
303
- // Empty dependency array so this will only run once after first render.
304
- React.useLayoutEffect(() => {
305
- elementRef.current?.removeAttribute("defer-hydration");
306
- }, []);
307
- }
308
-
309
- if (NODE_MODE) {
310
- // If component is to be server rendered with `@lit/ssr-react`, pass
311
- // element properties in a special bag to be set by the server-side
312
- // element renderer.
313
- if (
314
- (React.createElement.name === "litPatchedCreateElement" ||
315
- // @ts-expect-error globalThis is any
316
- globalThis.litSsrReactEnabled) &&
317
- Object.keys(elementProps).length
318
- ) {
319
- // This property needs to remain unminified.
320
- reactProps._$litProps$ = elementProps;
321
- }
322
- } else {
323
- // Suppress hydration warning for server-rendered attributes.
324
- // This property needs to remain unminified.
325
- reactProps.suppressHydrationWarning = true;
326
- }
327
-
328
- return React.createElement(tagName, {
329
- ...reactProps,
330
- ref: React.useCallback(
331
- (node: I) => {
332
- elementRef.current = node;
333
- if (typeof ref === "function") {
334
- ref(node);
335
- } else if (ref !== null) {
336
- ref.current = node;
337
- }
338
- },
339
- [ref],
340
- ),
341
- });
342
- });
343
-
344
- ReactComponent.displayName = displayName ?? elementClass.name;
345
-
346
- return ReactComponent;
347
- };
package/src/index.ts DELETED
@@ -1,11 +0,0 @@
1
- export { EFAudio } from "./elements/EFAudio";
2
- export { EFCaptions, EFCaptionsActiveWord } from "./elements/EFCaptions";
3
- export { EFImage } from "./elements/EFImage";
4
- export { EFTimegroup } from "./elements/EFTimegroup";
5
- export { EFVideo } from "./elements/EFVideo";
6
- export { EFWaveform } from "./elements/EFWaveform";
7
-
8
- export { EFWorkbench } from "./gui/EFWorkbench";
9
- export { EFFilmstrip } from "./gui/EFFilmstrip";
10
-
11
- export { useTimingInfo } from "./hooks/useTimingInfo";
@@ -1,10 +0,0 @@
1
- import type { Config } from "tailwindcss";
2
-
3
- module.exports = {
4
- content: ["./src/**/*.ts"],
5
-
6
- theme: {
7
- extend: {},
8
- },
9
- plugins: [],
10
- } satisfies Config;
package/tsconfig.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "include": ["src/**/*.ts"],
3
- "extends": "../../tsconfig.json"
4
- }
package/vite.config.ts DELETED
@@ -1,8 +0,0 @@
1
- import path from "node:path";
2
-
3
- import { defineViteBuildConfig } from "../defineViteBuildConfig";
4
-
5
- export default defineViteBuildConfig({
6
- root: path.dirname(new URL(import.meta.url).pathname),
7
- name: "editframe-react",
8
- });