Skip to content

Commit

Permalink
feat: Refactor to use ce-la-react to add type definitions for JSX att…
Browse files Browse the repository at this point in the history
…ributes (#1068)

This PR fix this
[issue](#1007) by:

- Add [ce-la-react](https://github.com/muxinc/ce-la-react)
- Modify the build.js in react to use ce-la-react
- Add attributes as properties to create type definitions
- Remove the creation of the d.ts files in build since the new
implementation generate them.
- move some components imports to be found in ce-la-react

Note: Styles inline need to be set as any to don't have a warning from
typescript error.

![Screenshot 2025-02-05 at 4 06
15 PM](https://github.com/user-attachments/assets/c5f3c1c2-f628-4209-a38e-55b567d3d244)

---------

Co-authored-by: Wesley Luyten <me@wesleyluyten.com>
  • Loading branch information
ronalduQualabs and luwes committed Feb 25, 2025
1 parent 13c5543 commit d20dd95
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 105 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ sandbox/*
## testing
/coverage/
*.sublime-*
package-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const Player = () => {
</span>
</div>
<br />
{mounted && (<MediaController style={chromeStyles} defaultSubtitles noDefaultStore={noDefaultStore}>
{mounted && (<MediaController hotkeys={"noarrowleft noarrowright"} style={chromeStyles as any} gesturesDisabled defaultSubtitles noDefaultStore={noDefaultStore}>
<video
suppressHydrationWarning
slot="media"
Expand Down Expand Up @@ -80,9 +80,9 @@ export const Player = () => {
></MediaPosterImage>
<MediaLoadingIndicator
suppressHydrationWarning
noautohide
noAutohide
slot="centered-chrome"
style={{ '--media-loading-indicator-icon-height': '200px' }}
style={{ '--media-loading-indicator-icon-height': '200px' } as any}
></MediaLoadingIndicator>
<MediaPlaybackRateMenu hidden anchor="auto" />
<MediaControlBar>
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -184,5 +184,8 @@
"media",
"player",
"controls"
]
],
"dependencies": {
"ce-la-react": "^0.1.3"
}
}
114 changes: 17 additions & 97 deletions scripts/react/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { fileURLToPath } from 'url';
import path from 'path';
import fs from 'fs';
const { dirname } = path;
import { GlobalThis } from '../../dist/utils/server-safe-globals.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Expand All @@ -22,82 +23,27 @@ const toPascalCase = (kebabText) => {
return kebabText.replace(/(^\w|-\w)/g, clearAndUpper);
};

const toImportsStr = ({ importPath, utilsBase }) => {
const toImportsStr = ({ importPath }) => {
return `import React from "react";
import "${importPath}";
import { toNativeProps } from "${utilsBase}/common/utils.js";
`;
import { createComponent } from 'ce-la-react';
import * as Modules from "${importPath}"`;
};

const toReactComponentStr = (config) => {
const { elementName } = config;
const ReactComponentName = toPascalCase(elementName);
return `/** @type { import("react").HTMLElement } */
const ${ReactComponentName} = React.forwardRef(({ children = [], ...props }, ref) => {
return React.createElement('${elementName}', { ...toNativeProps(props), ref, suppressHydrationWarning: true }, children);
return `
export const ${ReactComponentName} = createComponent({
tagName: "${elementName}",
elementClass: Modules.${ReactComponentName},
react: React,
});`;
};

const toExportsStr = (config) => {
const { elementName } = config;
const ReactComponentName = toPascalCase(elementName);
return `export { ${ReactComponentName} };`;
};

const toCustomElementReactWrapperModule = (config) => {
const moduleStr = `${toReactComponentStr(config)}
${toExportsStr(config)}
`;

return moduleStr;
};
// REACT MODULE STRING CREATION CODE END

// TYPESCRIPT DECLARATION FILE STRING CREATION CODE BEGIN
const toTypeImportsAndGenericDefinitionsStr = () => {
return `import type React from 'react';
import type * as CSS from 'csstype';
declare global {
interface Element {
slot?: string;
}
}
declare module 'csstype' {
interface Properties {
// Should add generic support for any CSS variables
[index: \`--\${string}\`]: any;
}
}
type GenericProps = { [k: string]: any };
type GenericElement = HTMLElement;
type GenericForwardRef = React.ForwardRefExoticComponent<
GenericProps & React.RefAttributes<GenericElement | undefined>
>;
`;
};

const toDeclarationStr = (config) => {
const { elementName } = config;
const ReactComponentName = toPascalCase(elementName);
return `declare const ${ReactComponentName}: GenericForwardRef;`;
return `${toReactComponentStr(config)}`;
};

const toCustomElementReactTypeDeclaration = (config) => {
const typeDeclarationStr = `${toDeclarationStr(config)}
${toExportsStr(config)}
`;

return typeDeclarationStr;
};
// TYPESCRIPT DECLARATION FILE STRING CREATION CODE END

// BUILD BEGIN

const entryPointsToReactModulesIterable = (
entryPoints,
{ getDefinedCustomElements, distRoot }
Expand Down Expand Up @@ -125,11 +71,6 @@ const entryPointsToReactModulesIterable = (
name,
ext: '.js',
});
const tsDeclPathAbs = path.format({
dir: distReactRoot,
name,
ext: '.d.ts',
});

return import(importPath)
.then((_) => {
Expand All @@ -150,33 +91,19 @@ const entryPointsToReactModulesIterable = (
fs.mkdirSync(path.dirname(modulePathAbs), { recursive: true });

const importPathRelative = path.relative(distReactRoot, importPathAbs);
const utilsBase = path.dirname(path.relative(importPathAbs, distRoot));
const moduleStr = `${toImportsStr({
importPath: importPathRelative,
utilsBase,
importPath: importPathRelative
})}\n${componentsWithExports.join('\n')}`;

fs.writeFileSync(modulePathAbs, moduleStr);

const declarationsWithExports = undefinedCustomElementNames.map(
(elementName) => {
return toCustomElementReactTypeDeclaration({ elementName });
}
);

const tsDeclStr = `${toTypeImportsAndGenericDefinitionsStr()}\n${declarationsWithExports.join(
'\n'
)}`;

fs.writeFileSync(tsDeclPathAbs, tsDeclStr);

alreadyDefinedCustomElementNames = [...customElementNames];

return {
modulePath: modulePathAbs,
moduleContents: moduleStr,
tsDeclarationPath: tsDeclPathAbs,
tsDeclarationContents: tsDeclStr,
};
})
.then((moduleDef) => {
Expand Down Expand Up @@ -227,7 +154,7 @@ const createReactWrapperModules = async ({

try {
for await (let moduleDef of moduleCreateAsyncIterable) {
const { modulePath, moduleContents } = moduleDef;
const { modulePath } = moduleDef;
console.log(
'React module wrapper created!',
'path (absolute):',
Expand Down Expand Up @@ -257,18 +184,11 @@ const entryPoints = [
path.join(projectRoot, 'dist', 'media-theme-element.js')
];
const setupGlobalsAsync = async () => {
const customElementNames = await import(
path.join(projectRoot, 'dist', 'utils', 'server-safe-globals.js')
).then((exports) => {
Object.assign(globalThis, exports.globalThis);
globalThis.customElementNames = [];
globalThis.customElements.define = (name, _classRef) =>
globalThis.customElementNames.push(name);
// NOTE: The current implementation relies on the fact that `customElementNames` will be mutated
// to add the Custom Element html name for every element that's defined as a result of loading/importing the entryPoints modules (CJP).
return globalThis.customElementNames;
});
return customElementNames;
const globalThis = GlobalThis;
globalThis.customElementNames = [];
globalThis.customElements.define = (name, _classRef) =>
globalThis.customElementNames.push(name);
return globalThis.customElementNames;
};

createReactWrapperModules({ entryPoints, setupGlobalsAsync, distRoot });
Expand Down
4 changes: 4 additions & 0 deletions src/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import MediaTimeDisplay from './media-time-display.js';
import MediaTimeRange from './media-time-range.js';
import MediaTooltip from './media-tooltip.js';
import MediaVolumeRange from './media-volume-range.js';
import MediaContainer from './media-container.js';
import MediaTextDisplay from './media-text-display.js';

export {
MediaAirplayButton,
Expand Down Expand Up @@ -62,4 +64,6 @@ export {
MediaTimeRange,
MediaTooltip,
MediaVolumeRange,
MediaContainer,
MediaTextDisplay,
};
36 changes: 34 additions & 2 deletions src/js/media-chrome-button.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { MediaStateReceiverAttributes } from './constants.js';
import MediaTooltip, { TooltipPlacement } from './media-tooltip.js';
import {
getBooleanAttr,
getOrInsertCSSRule,
getStringAttr,
setBooleanAttr,
setStringAttr,
} from './utils/element-utils.js';
import { globalThis, document } from './utils/server-safe-globals.js';

const Attributes = {
TOOLTIP_PLACEMENT: 'tooltipplacement',
DISABLED: 'disabled',
NO_TOOLTIP: 'notooltip',
};

const template = document.createElement('template');
Expand Down Expand Up @@ -72,7 +76,9 @@ template.innerHTML = /*html*/ `
}
media-tooltip {
${/** Make sure unpositioned tooltip doesn't cause page overflow (scroll). */ ''}
${
/** Make sure unpositioned tooltip doesn't cause page overflow (scroll). */ ''
}
max-width: 0;
overflow-x: clip;
opacity: 0;
Expand Down Expand Up @@ -195,7 +201,7 @@ class MediaChromeButton extends globalThis.HTMLElement {
// Conditional chaining accounts for scenarios
// where the tooltip element isn't yet defined.
this.tooltipEl?.updateXOffset?.();
}
};

// NOTE: There are definitely some "false positive" cases with multi-key pressing,
// but this should be good enough for most use cases.
Expand Down Expand Up @@ -274,6 +280,8 @@ class MediaChromeButton extends globalThis.HTMLElement {

if (!this.hasAttribute('disabled')) {
this.enable();
} else {
this.disable();
}

this.setAttribute('role', 'button');
Expand Down Expand Up @@ -331,6 +339,30 @@ class MediaChromeButton extends globalThis.HTMLElement {
setStringAttr(this, Attributes.TOOLTIP_PLACEMENT, value);
}

get mediaController(): string | undefined {
return getStringAttr(this, MediaStateReceiverAttributes.MEDIA_CONTROLLER);
}

set mediaController(value: string | undefined) {
setStringAttr(this, MediaStateReceiverAttributes.MEDIA_CONTROLLER, value);
}

get disabled(): boolean | undefined {
return getBooleanAttr(this, Attributes.DISABLED);
}

set disabled(value: boolean | undefined) {
setBooleanAttr(this, Attributes.DISABLED, value);
}

get noTooltip(): boolean | undefined {
return getBooleanAttr(this, Attributes.NO_TOOLTIP);
}

set noTooltip(value: boolean | undefined) {
setBooleanAttr(this, Attributes.NO_TOOLTIP, value);
}

/**
* @abstract
* @argument {Event} e
Expand Down
54 changes: 54 additions & 0 deletions src/js/media-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import { nouns } from './labels/labels.js';
import { observeResize, unobserveResize } from './utils/resize-observer.js';
// Guarantee that `<media-gesture-receiver/>` is available for use in the template
import './media-gesture-receiver.js';
import {
getBooleanAttr,
getStringAttr,
setBooleanAttr,
setStringAttr,
} from './utils/element-utils.js';

export const Attributes = {
AUDIO: 'audio',
Expand Down Expand Up @@ -638,6 +644,54 @@ class MediaContainer extends globalThis.HTMLElement {
get autohide(): string {
return (this.#autohide === undefined ? 2 : this.#autohide).toString();
}

get breakpoints(): string | undefined {
return getStringAttr(this, Attributes.BREAKPOINTS);
}

set breakpoints(value: string | undefined) {
setStringAttr(this, Attributes.BREAKPOINTS, value);
}

get audio(): boolean | undefined {
return getBooleanAttr(this, Attributes.AUDIO);
}

set audio(value: boolean | undefined) {
setBooleanAttr(this, Attributes.AUDIO, value);
}

get gesturesDisabled(): boolean | undefined {
return getBooleanAttr(this, Attributes.GESTURES_DISABLED);
}

set gesturesDisabled(value: boolean | undefined) {
setBooleanAttr(this, Attributes.GESTURES_DISABLED, value);
}

get keyboardControl(): boolean | undefined {
return getBooleanAttr(this, Attributes.KEYBOARD_CONTROL);
}

set keyboardControl(value: boolean | undefined) {
setBooleanAttr(this, Attributes.KEYBOARD_CONTROL, value);
}

get noAutohide(): boolean | undefined {
return getBooleanAttr(this, Attributes.NO_AUTOHIDE);
}

set noAutohide(value: boolean | undefined) {
setBooleanAttr(this, Attributes.NO_AUTOHIDE, value);
}

get userInteractive(): boolean | undefined {
return getBooleanAttr(this, Attributes.USER_INACTIVE);
}

set userInteractive(value: boolean | undefined) {
setBooleanAttr(this, Attributes.USER_INACTIVE, value);
}
}

if (!globalThis.customElements.get('media-container')) {
Expand Down
Loading

0 comments on commit d20dd95

Please sign in to comment.