Skip to content

Commit

Permalink
fix/break: optional prop syntax for strictNullChecks=false tsconfig
Browse files Browse the repository at this point in the history
  • Loading branch information
dominiksta committed Nov 6, 2023
1 parent b1f9b0b commit bc264ac
Show file tree
Hide file tree
Showing 20 changed files with 100 additions and 87 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 3 additions & 7 deletions packages/core/cypress/component/attribute-reflection.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,9 @@ describe('attribute reflection', () => {
static useShadow = false;

props = {
attrBool: new rx.PropWithDefault<boolean>(
false, { reflect: true, converter: Boolean }
),
attrString: new rx.PropWithDefault<string>('hi', { reflect: true }),
attrNum: new rx.PropWithDefault<number>(
5, { reflect: true, converter: Number }
),
attrBool: rx.prop<boolean>({ reflect: true, defaultValue: false }),
attrString: rx.prop<string>({ reflect: true, converter: String, defaultValue: 'hi' }),
attrNum: rx.prop<number>({ reflect: true, defaultValue: 5 }),
}
render = () => [];
};
Expand Down
2 changes: 1 addition & 1 deletion packages/core/cypress/component/bindings.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { attempt, mount } from "../support/helpers";

@Component.register
class MyBoundInput extends Component {
props = { value: new rx.Prop<string>() };
props = { value: rx.prop<string>() };

render = () => [
h.input({
Expand Down
2 changes: 1 addition & 1 deletion packages/core/cypress/component/context.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class MySelect extends Component {
@Component.register
class MySelectItem extends Component {
props = {
value: new rx.Prop<string>(),
value: rx.prop<string>(),
}

render() {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/cypress/component/generics.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('generic components', () => {
}
}> {
props = {
value: new rx.Prop<T | undefined>(undefined)
value: rx.prop<T>({optional: true})
}
render = () => [];
}
Expand Down
23 changes: 13 additions & 10 deletions packages/core/cypress/component/props.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@ import { attempt, mount } from "../support/helpers";
class DumbComponent extends Component {

props = {
required: new rx.Prop<string>({ reflect: true }),
withDefault: new rx.PropWithDefault('default', { reflect: true }),
optional: new rx.Prop<string | undefined>({ reflect: true }),
required: rx.prop<string>({ reflect: true }),
withDefault: rx.prop({ reflect: true, defaultValue: 'default' }),
optional: rx.prop<string>({ reflect: true, optional: true }),
};

render = () => [
h.fieldset([
h.legend('I am a dumb Component'),
h.p(this.props.withDefault.map(v => `I render this value: ${v}`))
])
]
render() {
return [
h.fieldset([
h.legend('I am a dumb Component'),
h.p(this.props.required.map(v => `required: ${v}`)),
h.p(this.props.withDefault.map(v => `withDefault: ${v}`)),
h.p(this.props.optional.map(v => `optional: ${v}`)),
])
]
}
}

@Component.register
Expand Down Expand Up @@ -83,4 +87,3 @@ describe('props', (() => {
expect(attr(2, 'optional')).to.eq('second reactive value from attribute');
}))
}))

2 changes: 1 addition & 1 deletion packages/core/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ type MyEvents<T> = {

class GenericComp<T> extends Component<MyEvents<T>> {
props = {
value: new rx.Prop<T | undefined>(undefined)
value: rx.prop<T>({ optional: true })
}
render = () => [];
}
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import {
} from "./template-element";
import { Constructor, MaybeSubscribable, ToStringable } from "./util/types";
import {
Stream, State, Prop, Context, fromAllEvents, fromEvent, map,
Stream, State, Context, fromAllEvents, fromEvent, map,
distinctUntilChanged, skip, MulticastStream, throttleTime, debounceTime
} from "./rx";
import { camelToDash } from "./util/strings";
import { MVUI_GLOBALS } from "./globals";
import * as style from "./style";
import { isBinding } from "./rx/bind";
import { isSubscribable, Subscribable } from "./rx/interface";
import { Prop } from "./rx/prop";

// these symbols are used for properties that should be accessible from anywhere in mvui
// but should not be part of api surface
Expand Down Expand Up @@ -377,7 +378,7 @@ export default abstract class Component<
* @example
* ```typescript
* class _MyComponent extends Component {
* props = { value: new rx.Prop(0) };
* props = { value: rx.prop<number>() };
* }
* const MyComponent _MyComponent.export();
* export default MyComponent;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/rx/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import type Component from "../component";
@Component.register
class CtxConsumer extends Component {
props = { value: new rx.Prop('') }
props = { value: rx.prop<string>() }
render() {
const ctx = this.getContext(myCtx);
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/rx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export { default as MulticastStream } from "./multicast-stream";
export { default as ReplayStream } from "./replay-stream";
export { default as State, LinkedState } from "./state";
export { derive, DerivedState } from "./derived-state";
export { default as Prop, PropWithDefault } from "./prop";
export { default as prop } from "./prop";
export { default as bind } from "./bind";
export { default as Context } from "./context";
export { default as Store } from "./store";
Expand Down
64 changes: 39 additions & 25 deletions packages/core/src/rx/prop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,36 @@ import State from "./state";

function identity<T>(v: T) { return v; };

export default class Prop<T> extends State<T> {
type PropOptions<T> = {
reflect?: boolean | string,
converter?: {
toString: (v: T) => string, fromString: (v: string) => T
} | StringConstructor | NumberConstructor |
BooleanConstructor | ObjectConstructor
};

export default function prop<T>(
options?: PropOptions<T>
): Prop<T>;
export default function prop<T>(
options?: PropOptions<T> & { optional: true }
): OptionalProp<T>;
export default function prop<T>(
options?: PropOptions<T> & { defaultValue: T }
): OptionalProp<T>;

export default function prop<T>(
options?: PropOptions<T> & { optional?: true, defaultValue?: T}
): Prop<T> | OptionalProp<T> {
if (options !== undefined) {
if ('optional' in options) return new OptionalProp<T>(undefined, options);
if ('defaultValue' in options) return new OptionalProp<T>(options.defaultValue, options);
}
return new Prop<T>(undefined, options);
}


export class Prop<T> extends State<T> {

/** @ignore */
_options: {
Expand All @@ -16,17 +45,13 @@ export default class Prop<T> extends State<T> {
}

constructor(
options?: {
reflect?: boolean | string,
converter?: {
toString: (v: T) => string, fromString: (v: string) => T
} | StringConstructor | NumberConstructor |
BooleanConstructor | ObjectConstructor
},
initial?: T,
options?: PropOptions<T>,
) {
// we can cast to any here because we will fill the initial value for a required prop
// before rendering anyway.
super(undefined as any);
if (initial !== undefined) this.next(initial);

if (options) {
if (options.reflect) this._options.reflect = options.reflect;
Expand Down Expand Up @@ -61,27 +86,16 @@ export default class Prop<T> extends State<T> {
toString: JSON.stringify,
}
}
} else {
this._options.converter = {
fromString: JSON.parse,
toString: JSON.stringify,
}
}
}

}
}

export class PropWithDefault<T> extends Prop<T> {
private __marker = true;

constructor(
initial: T,
options?: {
reflect?: boolean | string,
converter?: {
toString: (v: T) => string, fromString: (v: string) => T
} | StringConstructor | NumberConstructor |
BooleanConstructor | ObjectConstructor
},
) {
super(options);
this.next(initial);
}
}
export class OptionalProp<T> extends Prop<T> { private __optionalPropMarker = Symbol(); }

30 changes: 15 additions & 15 deletions packages/core/src/template-element.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { MvuiCSSSheet } from "./style";
import { isSubscribable } from "./rx/interface";
import { MaybeSubscribable, ToStringable, UndefinedToOptional } from "./util/types";
import { Prop, PropWithDefault } from "./rx";
import { MaybeSubscribable, ToStringable } from "./util/types";
import { Prop, OptionalProp } from "./rx/prop";

export interface EventWithTarget<T extends HTMLElement> extends Event {
target: T;
Expand Down Expand Up @@ -159,20 +159,20 @@ export type TemplateElementParams<
[Property in keyof T]: MaybeSubscribable<T[Property]>
}>,

// Props are always optional for a technical reason: A webcomponent may be
// instantiated from a call to document.createElement, which does not have the ability
// to pass anything to the constructor. Therefore, any webcomponent must have some
// valid initial state and props must always be optional.
props: Props extends undefined ? never : {
[P in keyof PropsDefaultToOptional<Props>]:
MaybeSubscribable<Props[P] extends Prop<infer I> ? I : never>
}
props: Props extends undefined ? never : PropsDefToTemplate<Props>

ref?: { current: HTMLElement },
};

type PropsDefaultToOptional<T extends { [key: string]: Prop<any>}> = UndefinedToOptional<{
[P in keyof T]:
(T[P] extends Prop<infer I> ? I : never)
| (T[P] extends PropWithDefault<any> ? undefined : never)
}>;
type GetMandatoryPropKeys<T> = {
[P in keyof T]: T[P] extends OptionalProp<any> ? never : P
}[keyof T];

type MakeOptionalProps<T> = Partial<T> & Pick<T, GetMandatoryPropKeys<T>>;

type PropsExtractGeneric<T> = {
[P in keyof T]: Exclude<T[P], undefined> extends Prop<infer I>
? MaybeSubscribable<I> : never
};

type PropsDefToTemplate<T extends { [key: string]: Prop<any>}> = PropsExtractGeneric<MakeOptionalProps<T>>;
3 changes: 3 additions & 0 deletions packages/core/src/util/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ type GetMandatoryKeys<T> = {
}[keyof T];

export type UndefinedToOptional<T> = Partial<T> & Pick<T, GetMandatoryKeys<T>>;

// can sometimes help clean up types
export type Expand<T> = { [P in keyof T]: T[P] };
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Props are the primary way of passing data *down* the component tree.


Props are declared with the `props` class field as objects of the type
[`rx.Prop`](/reference/rx/classes/prop/). You can then set them in a template elements
[`prop`](/reference/rx/functions/prop/). You can then set them in a template elements
parameters.

{{<hint info>}}
Expand All @@ -29,8 +29,8 @@ import { Component, rx, h } from "@mvui/core";
@Component.register
class MyButton extends Component {
props = {
kind: new rx.Prop<'primary' | 'default'>(
'default', { reflect: true } // false by default
kind: new rx.prop<'primary' | 'default'>(
{ reflect: true, defaultValue: 'default' } // false by default
),
}

Expand Down
6 changes: 3 additions & 3 deletions packages/docs/static/js/app/codeview/codeview.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ export default class Codeview extends Component {

props = {
// 'both' or 'tabs'
display: new rx.Prop('both'),
display: rx.prop({ defaultValue: 'both' }),
// 'output' or 'code', only makes sense when `display` is 'tabs'
initialTab: new rx.Prop('output'),
outputHeight: new rx.Prop('80px', { reflect: true }),
initialTab: rx.prop({ defaultValue: 'output'}),
outputHeight: rx.prop({ reflect: true, defaultValue: '80px' }),
}

#runCode(text) {
Expand Down
6 changes: 3 additions & 3 deletions packages/docs/static/js/app/codeview/sandbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ async function getToInject() {
export class Sandbox extends Component {

props = {
code: new rx.Prop(''),
height: new rx.Prop('80px'),
autoBackground: new rx.Prop(true),
code: rx.prop(),
height: rx.prop({ defaultValue: '80px' }),
autoBackground: rx.prop({ defaultValue: true }),
}

render() {
Expand Down
6 changes: 3 additions & 3 deletions packages/stdlib/src/components/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ export class Button extends Component<{
})

props = {
kind: new rx.Prop<
'default' | 'primary' | 'accent'
>('default', { reflect: true }),
kind: rx.prop<'default' | 'primary' | 'accent'>(
{ reflect: true, defaultValue: 'default' }
),
}

render() {
Expand Down
8 changes: 2 additions & 6 deletions packages/stdlib/src/components/collapsible.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,8 @@ export class Collapsible extends Component {
static tagNameLibrary = 'std';

props = {
initialCollapsed: new rx.Prop<boolean>(
false, { reflect: true, converter: Boolean }
),
collapsed: new rx.Prop<boolean>(
false, { reflect: true, converter: Boolean }
),
initialCollapsed: rx.prop<boolean>({ reflect: true, defaultValue: true }),
collapsed: rx.prop<boolean>({ reflect: true, defaultValue: false }),
}

render() {
Expand Down
4 changes: 2 additions & 2 deletions packages/stdlib/src/components/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export class Input extends Component {
});

props = {
value: new rx.Prop('', { reflect: true }),
onlyEmitOnBlur: new rx.Prop(false, { reflect: true, converter: Boolean }),
value: rx.prop<string>({ reflect: true }),
onlyEmitOnBlur: rx.prop({ reflect: true, defaultValue: false }),
};

render = () => [
Expand Down
2 changes: 1 addition & 1 deletion packages/stdlib/src/components/menu/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export class Menu extends Component {
static tagNameLibrary = 'std';

props = {
text: new rx.Prop('', { reflect: true }),
text: rx.prop<string>({ reflect: true }),
}


Expand Down

0 comments on commit bc264ac

Please sign in to comment.