Skip to content

Commit

Permalink
feat: handle Props from JSX
Browse files Browse the repository at this point in the history
  • Loading branch information
F0rsaken committed Oct 13, 2024
1 parent 4b834f1 commit 3156155
Show file tree
Hide file tree
Showing 15 changed files with 464 additions and 59 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"ts-jest": "^27.0.5",
"tslib": "^2.5.0",
"turbo": "^1.8.2",
"typescript": "^4.9.5",
"typescript": "^5.6.2",
"vitepress": "^1.0.0-rc.44",
"vitest": "^0.32.4"
},
Expand All @@ -54,4 +54,4 @@
"docs"
],
"packageManager": "[email protected]"
}
}
8 changes: 6 additions & 2 deletions packages/ovee/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@
"types": "dist/index.d.ts",
"exports": {
".": "./dist/index.mjs",
"./jsx-runtime": "./dist/jsx-runtime.js"
"./jsx-runtime": "./dist/jsx-runtime.js",
"./jsx-dev-runtime": "./dist/jsx-dev-runtime.js"
},
"typesVersions": {
"*": {
"jsx-runtime": [
"dist/jsx-runtime.d.ts"
],
"jsx-dev-runtime": [
"dist/jsx-dev-runtime.d.ts"
]
}
},
Expand All @@ -36,7 +40,7 @@
},
"devDependencies": {
"html-jsx": "^1.0.0",
"typescript": "^4.9.5",
"typescript": "^5.6.2",
"vitest": "^0.32.4"
},
"scripts": {
Expand Down
26 changes: 16 additions & 10 deletions packages/ovee/src/composables/utils/useComponentContext.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
import { injectComponentContext } from '@/core/component/componentContext';
import { ComponentOptions } from '@/core/component/defineComponent';
import { ComponentOptions, ComponentProps } from '@/core/component/defineComponent';
import { ComponentContext } from '@/core/component/types';
import { Logger } from '@/errors';

const logger = new Logger('useComponentContext');

export interface ComponentPublicInstance<
Root extends HTMLElement = HTMLElement,
Options extends ComponentOptions = ComponentOptions
> extends ComponentContext<Options> {
Options extends ComponentOptions = ComponentOptions,
Props extends ComponentProps = ComponentProps
> extends ComponentContext<Options, Props> {
element: Root;
}

export function useComponentContext<
Root extends HTMLElement = HTMLElement,
Options extends ComponentOptions = ComponentOptions
>(allowMissingContext?: boolean): ComponentPublicInstance<Root, Options>;
Options extends ComponentOptions = ComponentOptions,
Props extends ComponentProps = ComponentProps
>(allowMissingContext?: boolean): ComponentPublicInstance<Root, Options, Props>;

export function useComponentContext<
Root extends HTMLElement = HTMLElement,
Options extends ComponentOptions = ComponentOptions
>(allowMissingContext: true): ComponentPublicInstance<Root, Options> | null;
Options extends ComponentOptions = ComponentOptions,
Props extends ComponentProps = ComponentProps
>(allowMissingContext: true): ComponentPublicInstance<Root, Options, Props> | null;

export function useComponentContext<
Root extends HTMLElement = HTMLElement,
Options extends ComponentOptions = ComponentOptions
>(allowMissingContext = false): ComponentPublicInstance<Root, Options> | null {
Options extends ComponentOptions = ComponentOptions,
Props extends ComponentProps = ComponentProps
>(allowMissingContext = false): ComponentPublicInstance<Root, Options, Props> | null {
const instance = injectComponentContext();

if (!instance && !allowMissingContext) {
Expand All @@ -38,11 +42,13 @@ export function useComponentContext<

if (!instance) return null;

// TODO: use normal instance type
return {
element: instance.element as Root,
name: instance.name,
app: instance.app,
options: instance.options as Options,
element: instance.element as Root,
props: instance.props as Props,

emit: (...args) => instance.emit(...args),
on: (...args) => instance.on(...args),
Expand Down
2 changes: 1 addition & 1 deletion packages/ovee/src/composables/utils/useQuerySelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ function useQuerySelectorManager<El extends HTMLElement = HTMLElement>(
const queryManagersMap = new Map<ComponentInstance, QuerySelectorManager>();

class QuerySelectorManager extends MutationObserverManager {
connected = false;
override connected = false;
observer: MutationObserver;
callbacks: Array<() => void> = [];
observeOptions = observerOptions;
Expand Down
8 changes: 4 additions & 4 deletions packages/ovee/src/composables/utils/useSlots.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NOOP } from '@/constants';
import { ComponentInstance, injectComponentContext } from '@/core';
import { Logger } from '@/errors';
import { Children, Fiber, JSX_FRAGMENT } from '@/jsx';
import { Fiber, JSX_FRAGMENT, JSXElement } from '@/jsx';
import { getNoContextWarning } from '@/utils';

type StoredSlot = HTMLTemplateElement | Node[];
Expand All @@ -10,7 +10,7 @@ const logger = new Logger('useSlots');
const DEFAULT_SLOT_NAME = 'default';

// NOTE: may be built in useTemplate, for now it's seperate
export type SlotFunction = (name: string) => Children;
export type SlotFunction = (props: { name?: string }) => JSXElement;

const slotsMap = new Map<ComponentInstance, SlotFunction>();

Expand Down Expand Up @@ -38,7 +38,7 @@ function initializeSlots(instance: ComponentInstance): SlotFunction {
return initHTMLSlots(instance);
}

return (name): Children => {
return ({ name = DEFAULT_SLOT_NAME }) => {
const jsxSlot = instance.jsxSlot!.value;
const isFunction = typeof jsxSlot === 'function';

Expand Down Expand Up @@ -86,7 +86,7 @@ function initHTMLSlots(instance: ComponentInstance): SlotFunction {

storedSlots.clear();

return (name): Fiber => {
return ({ name = DEFAULT_SLOT_NAME }): Fiber => {
const node = slots.get(name);

return {
Expand Down
37 changes: 31 additions & 6 deletions packages/ovee/src/core/component/ComponentInternalInstance.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,36 @@
import { EffectScope, effectScope, ShallowRef, shallowRef } from '@vue/reactivity';
import { EffectScope, effectScope, reactive, ShallowRef, shallowRef } from '@vue/reactivity';

import { EventDelegate, EventDesc, ListenerOptions, TargetOptions } from '@/dom';
import { SlotChildren } from '@/jsx';
import { AnyFunction, EventBus, OmitNil, runThrowable } from '@/utils';
import { AnyFunction, AnyObject, Data, EventBus, OmitNil, runThrowable } from '@/utils';

import { App } from '../app';
import { provideComponentContext } from './componentContext';
import { Component, ComponentOptions, ComponentReturn } from './defineComponent';
import { NormalizedProps, PropsDefinition, resolvePropsValues } from './props';
import { ComponentContext, ComponentInstance, WithOveeInstances } from './types';

export class ComponentInternalInstance<
Root extends HTMLElement = HTMLElement,
Options extends ComponentOptions = ComponentOptions,
Props extends ComponentOptions = ComponentOptions,
Return extends ComponentReturn = ComponentReturn
> implements ComponentInstance<Root, Options>
> implements ComponentInstance<Root, Options, Props>
{
mounted = false;
beforeMountBus = new EventBus('onBeforeMount');
mountBus = new EventBus('onMounted');
unmountBus = new EventBus('onUnmounted');
props = reactive({}) as Props;
renderPromise?: Promise<void>;
jsxSlot?: ShallowRef<SlotChildren>;

readonly instance: OmitNil<Return>;
readonly eventDelegate: EventDelegate<this>;
readonly scope: EffectScope;

private cachedPropsDefaults: AnyObject = {};

get unmounted() {
return !this.mounted;
}
Expand All @@ -34,11 +39,16 @@ export class ComponentInternalInstance<
return this.element as Root & WithOveeInstances;
}

private get componentContext(): ComponentContext<Options> {
get rawProps(): NormalizedProps {
return this.component.__ovee_props;
}

private get componentContext(): ComponentContext<Options, Props> {
return {
name: this.name,
app: this.app,
options: this.options as Options,
props: this.props,

emit: (...args) => this.emit(...args),
on: (...args) => this.on(...args),
Expand All @@ -50,14 +60,17 @@ export class ComponentInternalInstance<
public name: string,
public element: Root,
public app: App,
public component: Component<Root, Options, Return>,
public component: Component<Root, Options, PropsDefinition, Return>,
public options: Options,
jsxSlot?: SlotChildren
jsxSlot?: SlotChildren,
preSetup?: (instance: ComponentInternalInstance) => void
) {
this.eventDelegate = new EventDelegate(element, this);

if (jsxSlot) this.jsxSlot = shallowRef(jsxSlot);

preSetup?.(this as any);

const cleanUp = provideComponentContext(this);

this.scope = effectScope(true);
Expand Down Expand Up @@ -123,4 +136,16 @@ export class ComponentInternalInstance<
emit<D = any>(eventDesc: EventDesc, detail?: D): void {
this.eventDelegate.emit(eventDesc, detail);
}

/**
* @returns omitted props, not included in generation
*/
updateProps(rawProps: Data): Data {
return resolvePropsValues(
rawProps,
this.props,
this.component.__ovee_props,
this.cachedPropsDefaults
);
}
}
85 changes: 69 additions & 16 deletions packages/ovee/src/core/component/defineComponent.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,110 @@
import { ElementFiber, ElementFiberProps, FiberFactory } from '@/jsx';
import { AnyObject, OmitNil } from '@/utils';
import { AnyObject, EmptyObject, OmitNil } from '@/utils';

import { ANONYMOUS_TAG, AnonymousElement } from './Anonymous';
import { ComponentInternalInstance } from './ComponentInternalInstance';
import { OveeCustomElement } from './getComponentCustomElement';
import {
NormalizedProps,
normalizeProps,
PropsDefinition,
PropsDefinitionToRawTypes,
} from './props';
import { injectTemplateContext } from './templateContext';
import { ComponentContext } from './types';

export type ComponentProps = AnyObject;
export type ComponentOptions = AnyObject;
export type ComponentReturn = AnyObject | void;
export type AnyComponent = Component<any, any, any>;
export type AnyComponent = Component<any, any, any, any>;

export interface ComponentDefineFunction<
Root extends HTMLElement = HTMLElement,
Options extends ComponentOptions = ComponentOptions,
Props extends PropsDefinition = PropsDefinition,
Return extends ComponentReturn = ComponentReturn
> {
(element: Root, context: ComponentContext<Options>): Return;
(element: Root, context: ComponentContext<Options, PropsDefinitionToRawTypes<Props>>): Return;
}

export type GetComponentInstance<C extends AnyComponent, Return = ReturnType<C>> = OmitNil<Return>;

export type GetComponentOptions<C extends AnyComponent> = Parameters<C>[1]['options'];

export type GetComponentProps<C extends AnyComponent> = Parameters<C>[1]['props'];
export type GetComponentRoot<C extends AnyComponent> = Parameters<C>[0];

export type GetComponentInternalInstance<C extends AnyComponent> = ComponentInternalInstance<
GetComponentRoot<C>,
GetComponentOptions<C>,
GetComponentProps<C>,
GetComponentInstance<C>
>;

export interface Component<
Root extends HTMLElement = HTMLElement,
O extends ComponentOptions = ComponentOptions,
P extends PropsDefinition = PropsDefinition,
R extends ComponentReturn = ComponentReturn
> extends ComponentDefineFunction<Root, O, R> {
> extends ComponentDefineFunction<Root, O, P, R> {
__ovee_component_definition: true;
jsx: FiberFactory;
__ovee_props: NormalizedProps;
jsx: FiberFactory<PropsDefinitionToRawTypes<P> & AnyObject>;
}

export function defineComponent<
Root extends HTMLElement = HTMLElement,
Options extends ComponentOptions = ComponentOptions,
Props extends PropsDefinition = PropsDefinition,
Return extends ComponentReturn = ComponentReturn
>(
props: Props,
component: ComponentDefineFunction<Root, Options, Props, Return>
): Component<Root, Options, Props, Return>;

export function defineComponent<
Root extends HTMLElement = HTMLElement,
Options extends ComponentOptions = ComponentOptions,
Return extends ComponentReturn = ComponentReturn
>(component: ComponentDefineFunction<Root, Options, Return>): Component<Root, Options, Return> {
const _component = component as Component<Root, Options, Return>;
>(
component: ComponentDefineFunction<Root, Options, EmptyObject, Return>
): Component<Root, Options, EmptyObject, Return>;

export function defineComponent<
Root extends HTMLElement = HTMLElement,
Options extends ComponentOptions = ComponentOptions,
Props extends PropsDefinition = PropsDefinition,
Return extends ComponentReturn = ComponentReturn
>(
firstArg: Props | ComponentDefineFunction<Root, Options, Props, Return>,
secondArg?: ComponentDefineFunction<Root, Options, Props, Return>
): Component<Root, Options, Props, Return> {
const _component = (typeof firstArg === 'function' ? firstArg : secondArg) as Component<
Root,
Options,
Props,
Return
>;
const props = (typeof firstArg === 'function' ? {} : firstArg) as Props;

_component.__ovee_component_definition = true;
_component.__ovee_props = normalizeProps(props);
_component.jsx = (baseProps, fiber): ElementFiber => {
const type = ANONYMOUS_TAG;
const { children = {} } = baseProps;
const props = { ...baseProps } as ElementFiberProps;
let props = baseProps as ElementFiberProps;

props.children = [];
delete baseProps.children;

// TODO: check update
if (fiber.effectTag !== 'PLACEMENT') {
const node = fiber.alternate?.firstChild?.node;

if (fiber.effectTag === 'UPDATE') {
const instance = (node as OveeCustomElement)._OveeInternalInstance;

if (instance && instance.jsxSlot) instance.jsxSlot.value = children;
if (instance) {
if (instance.jsxSlot) instance.jsxSlot.value = children;

props = instance.updateProps(props);
}
}

return { type, node, props };
Expand All @@ -74,17 +117,27 @@ export function defineComponent<
const node = <AnonymousElement>document.createElement(type);

// NOTE: try to get default options, if exist
const instance = new ComponentInternalInstance<Root, Options, Return>(
const instance = new ComponentInternalInstance<
Root,
Options,
PropsDefinitionToRawTypes<Props>,
Return
>(
'anonymous',
<Root>(<unknown>node),
ctx.app,
_component,
_component as AnyComponent,
{} as Options,
children
children,
instance => {
props = instance.updateProps(props);
}
);

node.setup(<any>instance);

console.log('-->', props);

return { type, node, props };
};

Expand Down
Loading

0 comments on commit 3156155

Please sign in to comment.