Skip to content

Commit

Permalink
fix(lr-upload-ctx-provider): improve event types
Browse files Browse the repository at this point in the history
  • Loading branch information
nd0ut committed Nov 29, 2023
1 parent 5ce88cb commit 2497a91
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 83 deletions.
5 changes: 3 additions & 2 deletions blocks/Config/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class ConfigClass extends Block {

ConfigClass.bindAttributes(attrStateMapping);

export const Config = ConfigClass;
/** @typedef {import('../../utils/mixinClass.js').MixinClass<typeof ConfigClass, import('../../types').ConfigType>} Config */

/** @typedef {typeof ConfigClass & import('../../types').ConfigType} Config */
// This is workaround for jsdoc that allows us to export extended class type along with the class itself
export const Config = /** @type {Config} */ (/** @type {unknown} */ (ConfigClass));
37 changes: 35 additions & 2 deletions blocks/DataOutput/DataOutput.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { applyStyles } from '@symbiotejs/symbiote';
* }} Output}
*/

export class DataOutput extends UploaderBlock {
class DataOutputClass extends UploaderBlock {
processInnerHtml = true;
requireCtxName = true;

Expand Down Expand Up @@ -148,7 +148,7 @@ export class DataOutput extends UploaderBlock {
}
}

DataOutput.dict = Object.freeze({
DataOutputClass.dict = Object.freeze({
SRC_CTX_KEY: '*outputData',
EVENT_NAME: 'lr-data-output',
FIRE_EVENT_ATTR: 'use-event',
Expand All @@ -158,3 +158,36 @@ DataOutput.dict = Object.freeze({
INPUT_NAME_ATTR: 'input-name',
INPUT_REQUIRED: 'input-required',
});

/**
* @typedef {import('../../utils/mixinClass.js').MixinClass<
* typeof DataOutputClass,
* {
* addEventListener(
* type: 'lr-data-output',
* listener: (
* e: CustomEvent<{
* timestamp: number;
* ctxName: string;
* data: Output;
* }>
* ) => void,
* options?: boolean | AddEventListenerOptions
* ): void;
* removeEventListener(
* type: 'lr-data-output',
* listener: (
* e: CustomEvent<{
* timestamp: number;
* ctxName: string;
* data: Output;
* }>
* ) => void,
* options?: boolean | EventListenerOptions
* ): void;
* }
* >}
* DataOutput
*/

export const DataOutput = /** @type {DataOutput} */ (/** @type {unknown} */ (DataOutputClass));
16 changes: 6 additions & 10 deletions blocks/ShadowWrapper/ShadowWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,14 @@ import { waitForAttribute } from '../../utils/waitForAttribute.js';
const CSS_ATTRIBUTE = 'css-src';

/**
* @template T
* @typedef {new (...args: any[]) => T} GConstructor
*/

/**
* @template {GConstructor<import('../../abstract/Block.js').Block>} T
* @template {import('../../utils/mixinClass.js').GConstructor<import('../../abstract/Block.js').Block>} T
* @param {T} Base
* @returns {{
* new (...args: ConstructorParameters<T>): InstanceType<T> & {
* @returns {import('../../utils/mixinClass.js').MixinClass<
* T,
* {
* shadowReadyCallback(): void;
* };
* } & Omit<T, 'new'>}
* }
* >}
*/
export function shadowed(Base) {
// @ts-ignore
Expand Down
40 changes: 21 additions & 19 deletions blocks/UploadCtxProvider/UploadCtxProvider.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// @ts-check

import { UploaderBlock } from '../../abstract/UploaderBlock.js';

class UploadCtxProviderClass extends UploaderBlock {
requireCtxName = true;

Expand All @@ -10,23 +9,26 @@ class UploadCtxProviderClass extends UploaderBlock {
}
}

export const UploadCtxProvider = UploadCtxProviderClass;

/**
* @typedef {typeof UploadCtxProviderClass & {
* addEventListener<
* T extends typeof import('./EventEmitter.js').EventType[keyof typeof import('./EventEmitter.js').EventType]
* >(
* type: T,
* listener: (e: CustomEvent<import('./EventEmitter.js').EventPayload[T]>) => void,
* options?: boolean | AddEventListenerOptions
* ): void;
* removeEventListener<
* T extends typeof import('./EventEmitter.js').EventType[keyof typeof import('./EventEmitter.js').EventType]
* >(
* type: T,
* listener: (e: CustomEvent<import('./EventEmitter.js').EventPayload[T]>) => void,
* options?: boolean | EventListenerOptions
* ): void;
* }} UploadCtxProvider
* @typedef {import('../../utils/mixinClass.js').MixinClass<
* typeof UploadCtxProviderClass,
* {
* addEventListener<
* T extends typeof import('./EventEmitter.js').EventType[keyof typeof import('./EventEmitter.js').EventType]
* >(
* type: T,
* listener: (e: CustomEvent<import('./EventEmitter.js').EventPayload[T]>) => void,
* options?: boolean | AddEventListenerOptions
* ): void;
* removeEventListener<
* T extends typeof import('./EventEmitter.js').EventType[keyof typeof import('./EventEmitter.js').EventType]
* >(
* type: T,
* listener: (e: CustomEvent<import('./EventEmitter.js').EventPayload[T]>) => void,
* options?: boolean | EventListenerOptions
* ): void;
* }
* >} UploadCtxProvider
*/

export const UploadCtxProvider = /** @type {UploadCtxProvider} */ (/** @type {unknown} */ (UploadCtxProviderClass));
17 changes: 11 additions & 6 deletions types/events.d.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import type { GlobalEventPayload } from '../blocks/UploadCtxProvider/EventEmitter';
import type { GlobalEventPayload, EventPayload } from '../blocks/UploadCtxProvider/EventEmitter';

type CustomEventMap = {
export type GlobalEventMap = {
[T in keyof GlobalEventPayload]: CustomEvent<GlobalEventPayload[T]>;
};

export type EventMap = {
[T in keyof EventPayload]: CustomEvent<EventPayload[T]>;
};

declare global {
interface Window {
addEventListener<T extends keyof CustomEventMap>(
addEventListener<T extends keyof GlobalEventMap>(
type: T,
listener: (e: CustomEventMap[T]) => void,
listener: (e: GlobalEventMap[T]) => void,
options?: boolean | AddEventListenerOptions
): void;
removeEventListener<T extends keyof CustomEventMap>(
removeEventListener<T extends keyof GlobalEventMap>(
type: T,
listener: (e: CustomEventMap[T]) => void,
listener: (e: GlobalEventMap[T]) => void,
options?: boolean | EventListenerOptions
): void;
}
Expand Down
22 changes: 11 additions & 11 deletions types/jsx.d.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/// <reference types="react" />

type ConfigPlainType = import('./exported').ConfigPlainType;
type UploadCtxProvider = import('..').UploadCtxProvider;
type ConfigPlainType = import('./exported.js').ConfigPlainType;
type UploadCtxProvider = import('../index.js').UploadCtxProvider;
type Config = import('../index.js').Config;
type FileUploaderInline = import('..').FileUploaderInline;
type FileUploaderRegular = import('..').FileUploaderRegular;
type FileUploaderMinimal = import('..').FileUploaderMinimal;
type DataOutput = import('..').DataOutput;
type CloudImageEditorBlock = import('..').CloudImageEditorBlock;
type FileUploaderInline = import('../index.js').FileUploaderInline;
type FileUploaderRegular = import('../index.js').FileUploaderRegular;
type FileUploaderMinimal = import('../index.js').FileUploaderMinimal;
type DataOutput = import('../index.js').DataOutput;
type CloudImageEditorBlock = import('../index.js').CloudImageEditorBlock;
type CtxAttributes = {
'ctx-name': string;
};
Expand Down Expand Up @@ -56,17 +56,17 @@ declare namespace JSX {
'lr-cloud-image-editor-activity': any;
'lr-cloud-image-editor-block': CustomElement<
CloudImageEditorBlock,
CtxAttributes & { uuid: string; 'cdn-url': string }
CtxAttributes & ({ uuid: string } | { 'cdn-url': string }) & Partial<{ tabs: string; 'crop-preset': string }>
>;
'lr-cloud-image-editor': CustomElement<
CloudImageEditorBlock,
CtxAttributes & ShadowWrapperAttributes & { uuid: string; 'cdn-url': string }
JSX.IntrinsicElements['lr-cloud-image-editor-block'] & ShadowWrapperAttributes
>;
'lr-data-output': CustomElement<DataOutput, CtxAttributes>;
'lr-file-uploader-regular': CustomElement<FileUploaderRegular, CtxAttributes & ShadowWrapperAttributes>;
'lr-file-uploader-minimal': CustomElement<FileUploaderMinimal, CtxAttributes & ShadowWrapperAttributes>;
'lr-file-uploader-inline': CustomElement<FileUploaderInline, CtxAttributes & ShadowWrapperAttributes>;
'lr-upload-ctx-provider': CustomElement<UploadCtxProvider, CtxAttributes>;
'lr-config': CustomElement<Config, CtxAttributes & Partial<ConfigPlainType>>;
'lr-upload-ctx-provider': CustomElement<InstanceType<UploadCtxProvider>, CtxAttributes>;
'lr-config': CustomElement<InstanceType<Config>, CtxAttributes & Partial<ConfigPlainType>>;
}
}
15 changes: 0 additions & 15 deletions types/test/events.test-d.ts

This file was deleted.

10 changes: 10 additions & 0 deletions types/test/global-events.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { expectType } from 'tsd';
import { GlobalEventPayload } from '../index.js';

window.addEventListener(
'LR_DATA_OUTPUT',
(e) => {
expectType<CustomEvent<GlobalEventPayload['LR_DATA_OUTPUT']>>(e);
},
{ once: true }
);
12 changes: 12 additions & 0 deletions types/test/lr-cloud-image-editor.test-d.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// @ts-expect-error - no props
() => <lr-cloud-image-editor />;

// @ts-expect-error - no css-url
() => <lr-cloud-image-editor ctx-name="my-uploader" />;

// @ts-expect-error - no css-src
() => <lr-cloud-image-editor css-src="url" />;

() => <lr-cloud-image-editor ctx-name="my-editor" css-src="url" uuid="123124" />;
() => <lr-cloud-image-editor ctx-name="my-editor" css-src="url" uuid="123124" tabs="tab" crop-preset="preset" />;
() => <lr-cloud-image-editor ctx-name="my-editor" css-src="url" cdn-url="url" />;
35 changes: 17 additions & 18 deletions types/test/lr-config.test-d.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { expectType } from 'tsd';
import '../jsx';
import { OutputFileEntry } from '..';
import '../jsx.js';
import { OutputFileEntry } from '../index.js';

// @ts-expect-error untyped props
() => <lr-config ctx-name="1" something="wrong"></lr-config>;
Expand All @@ -17,8 +17,8 @@ import { OutputFileEntry } from '..';

// allow useRef hook
() => {
const ref = React.useRef<Config | null>(null);
expectType<Config | null>(ref.current);
const ref = React.useRef<InstanceType<Config> | null>(null);
expectType<InstanceType<Config> | null>(ref.current);
<lr-config ctx-name="1" ref={ref}></lr-config>;
};

Expand All @@ -27,15 +27,15 @@ import { OutputFileEntry } from '..';
<lr-config
ctx-name="1"
ref={(el) => {
expectType<Config | null>(el);
expectType<InstanceType<Config> | null>(el);
}}
></lr-config>;
};

// allow createRef
() => {
const ref = React.createRef<Config>();
expectType<Config | null>(ref.current);
const ref = React.createRef<InstanceType<Config>>();
expectType<InstanceType<Config> | null>(ref.current);
<lr-config ctx-name="1" ref={ref}></lr-config>;
};

Expand All @@ -44,26 +44,25 @@ import { OutputFileEntry } from '..';

// allow to use DOM properties
() => {
const ref = React.useRef<Config | null>(null);
const ref = React.useRef<InstanceType<Config> | null>(null);
if (ref.current) {
const config = ref.current;
config.metadata = {foo: 'bar'}
config.secureSignature = '1231'
config.multiple = true
config.metadata = { foo: 'bar' };
config.secureSignature = '1231';
config.multiple = true;
}
};


// allow to pass metadata
() => {
const ref = React.useRef<Config | null>(null);
const ref = React.useRef<InstanceType<Config> | null>(null);
if (ref.current) {
const config = ref.current;
config.metadata = {foo: 'bar'}
config.metadata = () => ({foo: 'bar'})
config.metadata = { foo: 'bar' };
config.metadata = () => ({ foo: 'bar' });
config.metadata = async (entry) => {
expectType<OutputFileEntry>(entry)
return {foo: 'bar'}
}
expectType<OutputFileEntry>(entry);
return { foo: 'bar' };
};
}
};
18 changes: 18 additions & 0 deletions types/test/lr-data-output.test-d.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { expectType } from 'tsd';
import { DataOutput } from '../../index.js';
import { Output } from '../../blocks/DataOutput/DataOutput.js';

() => <lr-data-output ctx-name="my-uploader" />;

const dataOutput = new DataOutput();
dataOutput.addEventListener('lr-data-output', (e) => {
expectType<
CustomEvent<{
timestamp: number;
ctxName: string;
data: Output;
}>
>(e);
});

dataOutput.validationInput;
27 changes: 27 additions & 0 deletions types/test/lr-upload-ctx-provider.test-d.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { expectType } from 'tsd';
import { EventMap, UploadCtxProvider } from '../../index.js';
import { useRef } from 'react';

const instance = new UploadCtxProvider();

instance.addFileFromUrl('https://example.com/image.png');
instance.uploadCollection.size;
instance.setOrAddState('fileId', 'uploading');

instance.addEventListener('data-output', (e) => {
expectType<EventMap['data-output']>(e);

// @ts-expect-error - wrong event type
expectType<EventMap['init-flow']>(e);
});

const onDataOutput = (e: EventMap['data-output']) => {
// noop
};

instance.addEventListener('data-output', onDataOutput);

() => {
const ref = useRef<InstanceType<UploadCtxProvider>>(null);
return <lr-upload-ctx-provider ctx-name="ctx" ref={ref}></lr-upload-ctx-provider>;
};
18 changes: 18 additions & 0 deletions utils/mixinClass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @template T
* @typedef {new (...args: any[]) => T} GConstructor
*/

/**
* This is a helper to create a class type extended with the provided set of instance properties. It's useful when there
* are some dynamic generated properties or native overrides in the class. We're use it to define dynamic access
* properties and events to subscribe to.
*
* @template {GConstructor<HTMLElement>} Base
* @template {Record<string, any>} [InstanceProperties={}] Default is `{}`
* @typedef {{
* new (...args: ConstructorParameters<Base>): InstanceProperties & InstanceType<Base>;
* } & Omit<Base, 'new'>} MixinClass
*/

export {};

0 comments on commit 2497a91

Please sign in to comment.