Skip to content

Commit

Permalink
feat: complete types for modules and core
Browse files Browse the repository at this point in the history
Closes: #366
  • Loading branch information
hperrin committed Apr 21, 2020
1 parent 13fae7c commit 9b04b0f
Show file tree
Hide file tree
Showing 20 changed files with 159 additions and 68 deletions.
42 changes: 36 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
<img src="includes/logo.png" alt="PNotify" />
</div>

A JavaScript notification and [confirmation/prompt](http://sciactive.com/pnotify/#confirm-module) library.
A JavaScript/TypeScript notification, confirmation, and prompt library.

PNotify can provide [desktop notifications](http://sciactive.com/pnotify/#web-notifications) based on the [Web Notifications spec](http://www.w3.org/TR/notifications/) with fall back to an in-browser notice.
Notifications can display as toast style, snackbar style, banners, dialogs, alerts, or desktop notifications (using the [Web Notifications spec](http://www.w3.org/TR/notifications/)) with fall back to an in-browser notice.

PNotify implements a unique notification flow called [modalish](https://sciactive.com/2020/02/11/the-modalish-notification-flow/) that provides a good user experience, even when many notifications are shown at once.
PNotify provides a unique notification flow called [modalish](https://sciactive.com/2020/02/11/the-modalish-notification-flow/) that provides a good user experience, even when many notifications are shown at once.

<h1>Demos</h1>

Expand Down Expand Up @@ -42,6 +42,7 @@ Development - https://sciactive.github.io/pnotify/
- [Changing Defaults](#Changing-Defaults)
- [Modules](#Modules)
- [Creating Notices with Modules](#Creating-Notices-with-Modules)
- [TypeScript](#TypeScript)
- [Desktop Module](#Desktop-Module)
- [Mobile Module](#Mobile-Module)
- [Countdown Module](#Countdown-Module)
Expand Down Expand Up @@ -595,15 +596,15 @@ defaultModules.set(PNotifyMobile, {});

// Remove one of the default modules.
notice({
text: "I don't have the PNotifyMobile module.",
text: "I don't have the Mobile module.",
modules: new Map([
...[...defaultModules].filter(([mod]) => mod !== PNotifyMobile)
])
});

// Add an additional module and options.
notice({
text: "I use the PNotifyAnimate module in addition to the defaults.",
text: "I use the Animate module in addition to the defaults.",
modules: new Map([
...defaultModules,
[PNotifyAnimate, {
Expand All @@ -616,7 +617,7 @@ notice({
// Don't worry about adding a module that's already in the defaults.
// It's a Map, so only the last instance/options will end up in there.
notice({
text: "I use the PNotifyMobile module with options I specify.",
text: "I use the Mobile module with options I specify.",
modules: new Map([
...defaultModules,
[PNotifyMobile, {
Expand All @@ -626,6 +627,35 @@ notice({
});
```

### TypeScript

Using modules with TypeScript requires types assertions for module entries, and possibly the `downlevelIteration` TypeScript option.

```ts
import {notice, defaultModules, Notice, ModuleEntry} from '@pnotify/core';
import * as PNotifyConfirm from '@pnotify/confirm';

notice({
text: "I'm a notice with modules, and my module options are checked by TypeScript.",
modules: new Map([
// This requires `"downlevelIteration": true` in your TypeScript config.
...defaultModules,
[PNotifyConfirm, {
confirm: true,
buttons: [{
text: 'Ok',
primary: true,
click: (notice: Notice) => notice.close()
}]
// ***
// Notice the type assertion here. It tells TypeScript that the options
// are for the Confirm module.
// ***
}] as ModuleEntry<typeof PNotifyConfirm>,
])
});
```

## Desktop Module

Notifications that display even when the web page is not visible. Implements the [Web Notifications spec](http://www.w3.org/TR/notifications/).
Expand Down
90 changes: 73 additions & 17 deletions libtests/typescript/index.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,91 @@
import {notice, Stack, defaultStack} from '@pnotify/core';
import {info, Stack, defaultStack, defaultModules, Notice, ModuleEntry} from '@pnotify/core';
import * as PNotifyBootstrap4 from '@pnotify/bootstrap4';
import * as PNotifyAnimate from '@pnotify/animate';
import * as PNotifyConfirm from '@pnotify/confirm';

const myNotice = notice({
defaultModules.set(PNotifyBootstrap4, {});

// This *will not* produce an error, because TypeScript doesn't know the options
// are for PNotifyAnimate.
const entry: ModuleEntry = [PNotifyAnimate, {
inClass: 'someClass',
// This is bad, because if you get the options wrong or they change in a
// future version of PNotify, TypeScript won't tell you about it.
something: true,
}];

const entry2: ModuleEntry<typeof PNotifyAnimate> = [PNotifyAnimate, {
inClass: 'someClass',
// This *will* produce an error, as it should.
// something: true,
}];

const stack = new Stack({
dir1: 'up'
});

const myNotice = info({
text: 'Hello.',
hide: false,
closer: false,
sticker: false,
// This is good, because it's a Stack instance.
stack,
// This is bad, because it's just an object. This will produce an error.
// stack: { dir1: 'up' },
// This will produce an error, because it is the wrong type.
// delay: 'string',
modules: new Map([
// This should not produce a TypeScript error:
...defaultModules,
[PNotifyAnimate, {
inClass: 'someClass',
}] as PNotifyAnimate.Entry,
inClass: 'bounceIn',
outClass: 'bounceOut',
// This *will* produce a TypeScript error, as it should.
// something: true,
}] as ModuleEntry<typeof PNotifyAnimate>,
[PNotifyConfirm, {
confirm: true,
}] as PNotifyConfirm.Entry,
// This should produce a TypeScript error:
// [PNotifyAnimate, {
// inClass: 'someClass',
// something: 'hello',
// }] as PNotifyAnimate.Entry,
buttons: [
{
text: 'Hi',
primary: true,
click: (notice: Notice) => notice.close()
}
]
}] as ModuleEntry<typeof PNotifyConfirm>,
])
});

const stack = new Stack({
dir1: 'up'
});

// Hover over these in your IDE to see what TypeScript says about their value.
stack.dir1;
defaultStack.dir1;

// Unfortunately, TypeScript doesn't yet provide a way to spread the options
// over the notice, so here, TypeSript says `text` could be
// `string | false | HTMLElement`.
myNotice.text;
// But if you assign the value after the notice is created (which you probably
// shouldn't do usually),
myNotice.text = document.createElement('span');
// TypeScript knows now that it's an HTMLElement.
myNotice.text;
// And if you assign it back,
myNotice.text = 'Hello.';
// TypeScript knows it's a string.
myNotice.text;

myNotice.closer;
myNotice.type;
// It does know that `type` defaults to `"info"` here because we used the `info`
// function.
myNotice.type;


// Something you can do is to assert the type of the options on the notice.
const myOptions2 = {
text: 'Hello.'
};
const myNotice2 = info(myOptions2) as Notice & typeof myOptions2;

// Now TypeScript knows that `text` is a string.
myNotice2.text;
// But you can't then assign anything else. :(
// myNotice2.text = document.createElement('span'); // TypeScript error.
7 changes: 7 additions & 0 deletions libtests/typescript/package-lock.json

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

1 change: 1 addition & 0 deletions libtests/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"license": "Apache-2.0",
"devDependencies": {
"@pnotify/animate": "file:../../packages/animate",
"@pnotify/bootstrap4": "file:../../packages/bootstrap4",
"@pnotify/confirm": "file:../../packages/confirm",
"@pnotify/core": "file:../../packages/core",
"typescript": "^3.8.3"
Expand Down
3 changes: 2 additions & 1 deletion libtests/typescript/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"outDir": "dist",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
"forceConsistentCasingInFileNames": true,
"downlevelIteration": true
}
}
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,22 @@
"notifications",
"alert",
"alerts",
"web notification",
"web notifications",
"prompt",
"prompts",
"toast",
"toasts",
"snackbar",
"snackbars",
"dialog",
"dialogs",
"non blocking",
"notify",
"material",
"material ui"
"material ui",
"modal",
"modalish"
],
"homepage": "https://github.com/sciactive/pnotify",
"bugs": {
Expand Down
3 changes: 0 additions & 3 deletions packages/animate/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {Module, ModuleOptions} from '@pnotify/core';
import * as ModuleExport from './';

export as namespace PNotifyAnimate;

Expand All @@ -26,5 +25,3 @@ export default abstract class Animate extends ModuleProperties
implements Module {}
export const position: string;
export const defaults: ModuleProperties;

export type Entry = [typeof ModuleExport, Options];
3 changes: 0 additions & 3 deletions packages/bootstrap3/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {Module, ModuleOptions} from '@pnotify/core';
import * as ModuleExport from './';

export as namespace PNotifyBootstrap3;

Expand All @@ -11,5 +10,3 @@ export default abstract class Bootstrap3 extends ModuleProperties
implements Module {}
export const position: string;
export const defaults: ModuleProperties;

export type Entry = [typeof ModuleExport, Options];
3 changes: 0 additions & 3 deletions packages/bootstrap4/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {Module, ModuleOptions} from '@pnotify/core';
import * as ModuleExport from './';

export as namespace PNotifyBootsrap4;

Expand All @@ -11,5 +10,3 @@ export default abstract class Bootsrap4 extends ModuleProperties
implements Module {}
export const position: string;
export const defaults: ModuleProperties;

export type Entry = [typeof ModuleExport, Options];
3 changes: 0 additions & 3 deletions packages/confirm/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {Module, ModuleOptions, Notice} from '@pnotify/core';
import * as ModuleExport from './';

export as namespace PNotifyConfirm;

Expand Down Expand Up @@ -99,5 +98,3 @@ export default abstract class Confirm extends ModuleProperties
implements Module {}
export const position: string;
export const defaults: ModuleProperties;

export type Entry = [typeof ModuleExport, Options];
1 change: 0 additions & 1 deletion packages/core/Material.css
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,3 @@ Requires stylesheet to work: https://fonts.googleapis.com/css?family=Material+Ic
[data-pnotify] .material-error .material-countdown-bar {
background-color: var(--error-background-color);
}

35 changes: 29 additions & 6 deletions packages/core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,12 @@ export interface ModuleExport {
default: Module;
position: string;
defaults: ModuleOptions;
[k: string]: any;
}

type ModuleMap = Map<ModuleExport, ModuleOptions>;
export type ModuleEntry<T extends ModuleExport = ModuleExport> = [T, Partial<T['defaults']>];

export type ModuleMap = Iterable<ModuleEntry>;

export type Options = Partial<NoticeProperties>;

Expand Down Expand Up @@ -333,7 +336,7 @@ declare interface DefaultStack extends Stack {
}

export const defaultStack: DefaultStack;
export const defaultModules: ModuleMap;
export const defaultModules: Map<ModuleExport, ModuleOptions>;
export const defaults: NoticeProperties;

/**
Expand All @@ -345,19 +348,39 @@ export function alert(options: Options | string): Notice;
* Create a PNotify Notice with the type set to 'notice';
* @param options Notice options.
*/
export function notice(options: Options | string): Notice & { type: 'notice' };
export function notice(options: Options | string): Notice & {
/**
* @default 'notice'
*/
type: Options['type']
};
/**
* Create a PNotify Notice with the type set to 'info';
* @param options Notice options.
*/
export function info(options: Options | string): Notice & { type: 'info' };
export function info(options: Options | string): Notice & {
/**
* @default 'info'
*/
type: Options['type']
};
/**
* Create a PNotify Notice with the type set to 'success';
* @param options Notice options.
*/
export function success(options: Options | string): Notice & { type: 'success' };
export function success(options: Options | string): Notice & {
/**
* @default 'success'
*/
type: Options['type']
};
/**
* Create a PNotify Notice with the type set to 'error';
* @param options Notice options.
*/
export function error(options: Options | string): Notice & { type: 'error' };
export function error(options: Options | string): Notice & {
/**
* @default 'error'
*/
type: Options['type']
};
3 changes: 0 additions & 3 deletions packages/countdown/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {Module, ModuleOptions} from '@pnotify/core';
import * as ModuleExport from './';

export as namespace PNotifyCountdown;

Expand All @@ -25,5 +24,3 @@ export default abstract class Countdown extends ModuleProperties
implements Module {}
export const position: string;
export const defaults: ModuleProperties;

export type Entry = [typeof ModuleExport, Options];
3 changes: 0 additions & 3 deletions packages/desktop/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {Module, ModuleOptions} from '@pnotify/core';
import * as ModuleExport from './';

export as namespace PNotifyDesktop;

Expand Down Expand Up @@ -54,5 +53,3 @@ export default abstract class Desktop extends ModuleProperties
implements Module {}
export const position: string;
export const defaults: ModuleProperties;

export type Entry = [typeof ModuleExport, Options];
Loading

0 comments on commit 9b04b0f

Please sign in to comment.