chore(root): prepare workspace for alpha (#276)

This commit is contained in:
rahim
2025-12-21 20:26:13 +11:00
committed by GitHub
parent 5ddf02278d
commit b89fef56dd
247 changed files with 1197 additions and 446 deletions
@@ -0,0 +1,34 @@
export function printConsoleBanner(version: string): void {
// eslint-disable-next-line no-console
console.info(
`%c Video.js %c v${version}`,
`border-radius: 9999px;
background: #393836;
font: bold 1.5em/1.5em monospace;
color: #ebe4c1;
text-shadow: 1px 1px 0 #fcb116,
2px 2px 0 #f26222,
3px 3px 0 #ea3837,
4px 4px 0 #a83b71`,
`font: 1em monospace;`,
);
const prereleaseType = version.includes('preview') ? 'preview' : version.includes('alpha') ? 'alpha' : null;
if (prereleaseType) {
console.warn(
`%c This is a ${prereleaseType} release. Please use with caution.`,
`color: #f26222;`,
);
}
// eslint-disable-next-line no-console
console.info(
'%cReport a Bug, Issue or Feature Request - https://github.com/videojs/v10/issues/new/choose',
'color: #aaa; font-size: .9em;',
);
// eslint-disable-next-line no-console
console.info(
'%cReach out on Discord - https://discord.gg/JBqHh485uF',
'color: #aaa; font-size: .9em;',
);
}
@@ -0,0 +1,9 @@
let id = 0;
/**
* Generates a unique ID for an element.
*/
export function uniqueId(): string {
id++;
return `:h${id}:`;
}
@@ -0,0 +1,27 @@
export interface Point {
x: number;
y: number;
}
/**
* Get progress ratio of a point on a line segment.
* @param x - The x coordinate of the point.
* @param y - The y coordinate of the point.
* @param p1 - The first point of the line segment.
* @param p2 - The second point of the line segment.
*/
export function getPointProgressOnLine(x: number, y: number, p1: Point, p2: Point): number {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const lengthSquared = dx * dx + dy * dy;
if (lengthSquared === 0) return 0; // Avoid division by zero if p1 === p2
const projection = ((x - p1.x) * dx + (y - p1.y) * dy) / lengthSquared;
return Math.max(0, Math.min(1, projection)); // Clamp between 0 and 1
}
export function distance(p1: Point, p2: Point): number {
return Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
}
@@ -0,0 +1,71 @@
// Adapted from https://github.com/caiogondim/fast-memoize.js - MIT License
type MemoizedFunction<T extends (...args: any[]) => any> = (
...args: Parameters<T>
) => ReturnType<T>;
interface MemoizedId {
$m: number;
}
interface MemoizedWithId {
$m?: number;
}
function isNode(x: unknown): boolean {
if (typeof globalThis === 'undefined') return false;
const NodeConstructor = (globalThis as { Node?: new () => unknown }).Node;
return NodeConstructor !== undefined && x instanceof NodeConstructor;
}
export function memoize<T extends (...args: any[]) => any>(
func: T,
): MemoizedFunction<T> {
const cache: Record<string, ReturnType<T>> = {};
return function (this: unknown, ...args: Parameters<T>): ReturnType<T> {
const argsWithFuncIds = args.map((x) => {
if (isPlainObject(x) || Array.isArray(x)) {
const obj: Record<string, unknown> = {};
for (const key in x) {
obj[key] = memoizedIdFunc((x as Record<string, unknown>)[key]);
}
return obj;
}
return memoizedIdFunc(x);
});
const cacheKey = JSON.stringify(argsWithFuncIds);
const cachedValue = cache[cacheKey];
if (cachedValue !== undefined) {
return cachedValue;
}
const computedValue = func.apply(this, args);
cache[cacheKey] = computedValue;
return computedValue;
};
}
let id = 0;
function memoizedIdFunc(x: unknown): unknown {
if (typeof x === 'function' || isNode(x)) {
const funcOrNode = x as MemoizedWithId;
if (!funcOrNode.$m) funcOrNode.$m = ++id;
return { $m: funcOrNode.$m } as MemoizedId;
}
return x;
}
/**
* Check if this is a plain object.
* @param {object} obj - The object to inspect.
* @return {boolean}
*/
function isPlainObject(obj: unknown): obj is Record<string, unknown> {
if (typeof obj !== 'object' || obj === null) return false;
let proto = obj;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(obj) === proto;
}
@@ -0,0 +1,46 @@
/**
* Slightly modified version of React's shallowEqual, with optimizations for Arrays
* so we may treat them specifically as unequal if they are not a) both arrays
* or b) don't contain the same (shallowly compared) elements.
*/
export function shallowEqual(objA: object, objB: object): boolean {
// Using Object.is as a first pass, as it covers a lot of the "simple" cases that are
// more complex than strict equality and is a built-in. For discussion, see, e.g.:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#description
if (Object.is(objA, objB)) {
return true;
}
// Since we've done an Object.is() check immediately above, we can safely assume non-objects (or null-valued objects)
// are not equal, so can early bail for those as well.
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
return false;
}
if (Array.isArray(objA)) {
// Early "cheap" array compares
if (!Array.isArray(objB) || objA.length !== objB.length) return false;
// Shallow compare for arrays
return objA.every((vVal, i) => objB[i] === vVal);
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
// NOTE: Since we've already guaranteed the keys list lengths are the same, we can safely cast to string here (CJP)
if (
!globalThis.hasOwnProperty.call(objB, keysA[i] as string)
|| !Object.is(objA[keysA[i] as string], objB[keysA[i] as string])
) {
return false;
}
}
return true;
}
@@ -0,0 +1,23 @@
/**
* Converts a string to camel case.
*
* @param str - The string to convert.
* @returns The camel case string.
*/
export function toCamelCase(str: string): string {
return str
.toLowerCase()
.replace(/[-_]([a-z])/g, (_$0, $1) => $1.toUpperCase());
}
/**
* Converts a string to kebab case.
*
* @param str - The string to convert.
* @returns The kebab case string.
*/
export function toKebabCase(str: string): string {
return str
.replace(/([A-Z])/g, '-$1')
.toLowerCase();
}
@@ -0,0 +1,131 @@
/**
* Checks if a value is a valid number (not NaN, null, undefined, or Infinity)
*/
function isValidNumber(value: unknown): value is number {
return typeof value === 'number' && Number.isFinite(value);
}
const UnitLabels = [
{
singular: 'hour',
plural: 'hours',
},
{
singular: 'minute',
plural: 'minutes',
},
{
singular: 'second',
plural: 'seconds',
},
] as const;
function toTimeUnitPhrase(timeUnitValue: number, unitIndex: number): string {
const unitLabel = timeUnitValue === 1 ? UnitLabels[unitIndex]?.singular : UnitLabels[unitIndex]?.plural;
return `${timeUnitValue} ${unitLabel}`;
}
/**
* Converts numeric seconds into a human-readable phrase for accessibility.
*
* @param seconds - A (positive or negative) time, represented as seconds
* @returns The time, represented as a phrase of hours, minutes, and seconds
*
* @example
* formatAsTimePhrase(3661) // "1 hour, 1 minute, 1 second"
* formatAsTimePhrase(90) // "1 minute, 30 seconds"
* formatAsTimePhrase(-30) // "30 seconds remaining"
*/
export function formatAsTimePhrase(seconds: number): string {
if (!isValidNumber(seconds)) return '';
const positiveSeconds = Math.abs(seconds);
const negative = positiveSeconds !== seconds;
const secondsDateTime = new Date(0, 0, 0, 0, 0, positiveSeconds, 0);
const timeParts = [secondsDateTime.getHours(), secondsDateTime.getMinutes(), secondsDateTime.getSeconds()];
const timeString = timeParts
// Convert non-0 values to a string of the value plus its unit
.map((timeUnitValue, index) => timeUnitValue && toTimeUnitPhrase(timeUnitValue, index))
// Ignore/exclude any 0 values
.filter(x => x)
// join into a single comma-separated string phrase
.join(', ');
// If the time was negative, assume it represents some remaining amount of time/"count down".
const negativeSuffix = negative ? ' remaining' : '';
return `${timeString}${negativeSuffix}`;
}
/**
* Converts a time, in numeric seconds, to a formatted string representation
* of the form [HH:[MM:]]SS, where hours and minutes are optional, either
* based on the value of `seconds` or (optionally) based on the value of `guide`.
*
* @param seconds - The total time you'd like formatted, in seconds
* @param guide - A number in seconds that represents how many units you'd want
* to show. This ensures consistent formatting between e.g. 35s and 4834s.
* @returns A string representation of the time, with expected units
*
* @example
* formatTime(90) // "1:30"
* formatTime(3661) // "1:01:01"
* formatTime(35, 3600) // "0:35" (guided by 1-hour duration)
* formatTime(NaN) // "0:00"
* formatTime(Infinity) // "0:00"
*/
export function formatTime(seconds: number, guide?: number): string {
// Handle negative values
let negative = false;
if (seconds < 0) {
negative = true;
seconds = 0 - seconds;
}
seconds = seconds < 0 ? 0 : seconds;
let s: number | string = Math.floor(seconds % 60);
let m: number | string = Math.floor((seconds / 60) % 60);
let h: number | string = Math.floor(seconds / 3600);
const gm = guide ? Math.floor((guide / 60) % 60) : 0;
const gh = guide ? Math.floor(guide / 3600) : 0;
// Handle invalid times
if (Number.isNaN(seconds) || seconds === Infinity) {
// '-' is false for all relational operators (e.g. <, >=) so this setting
// will add the minimum number of fields specified by the guide
h = m = s = '0';
}
// Check if we need to show hours
const showHours = (h as number) > 0 || gh > 0;
const hoursString = showHours ? `${h}:` : '';
// If hours are showing, we may need to add a leading zero.
// Always show at least one digit of minutes.
const minutesString = `${(showHours || gm >= 10) && (m as number) < 10 ? `0${m}` : m}:`;
// Check if leading zero is needed for seconds
const secondsString = (s as number) < 10 ? `0${s}` : s;
return (negative ? '-' : '') + hoursString + minutesString + secondsString;
}
/**
* Formats a time value with fallback handling for invalid values.
*
* @param time - The time value to format in seconds (duration, currentTime, etc.)
* @param guide - Optional guide time for consistent formatting
* @param fallback - Fallback text when time is invalid (default: "--:--")
* @returns Formatted time string or fallback
*/
export function formatDisplayTime(time: unknown, guide?: number, fallback: string = '--:--'): string {
if (!isValidNumber(time)) {
return fallback;
}
return formatTime(time, guide);
}
@@ -0,0 +1,3 @@
export function isValidNumber(value: any): value is number {
return typeof value === 'number' && !Number.isNaN(value) && Number.isFinite(value);
}