diff --git a/README.md b/README.md index be34d8ee..258233db 100644 --- a/README.md +++ b/README.md @@ -458,9 +458,9 @@ PNotify options and default values. * `textTrusted: false`
Whether to trust the text or escape its contents. (Not allow HTML.) * `styling: 'brighttheme'`
- What styling classes to use. (Can be 'brighttheme', 'material', or a styling object.) (Note that the Bootstrap modules provide a different default.) + What styling classes to use. (Can be 'brighttheme', 'material', another string provided by a module, or a styling object.) * `icons: 'brighttheme'`
- What icons classes to use (Can be 'brighttheme', 'material', or an icon object.) (Note that the Font Awesome and Glyphicon modules provide a different default.) + What icons classes to use (Can be 'brighttheme', 'material', another string provided by a module, or an icon object.) * `mode: 'no-preference'`
Light or dark version of the theme, if supported by the styling. This overrides the CSS media query when a preference is given. (Can be 'no-preference', 'light', or 'dark'.) * `addClass: ''`
diff --git a/libtests/typescript/index.ts b/libtests/typescript/index.ts new file mode 100644 index 00000000..bf36225a --- /dev/null +++ b/libtests/typescript/index.ts @@ -0,0 +1,19 @@ +import {notice, Stack, defaultStack} from '@pnotify/core'; + +const myNotice = notice({ + text: 'Hello.' +}); + +const stack = new Stack({ + dir1: 'up' +}); + +stack.dir1; +defaultStack.dir1; + +myNotice.text; +myNotice.text = document.createElement('span'); +myNotice.text; + +myNotice.closer; +myNotice.type; \ No newline at end of file diff --git a/libtests/typescript/package-lock.json b/libtests/typescript/package-lock.json new file mode 100644 index 00000000..6d38e94d --- /dev/null +++ b/libtests/typescript/package-lock.json @@ -0,0 +1,22 @@ +{ + "name": "pnotify-typescript-test", + "version": "0.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@pnotify/core": { + "version": "file:../../packages/core", + "dev": true, + "requires": { + "@smui/common": "^1.0.0-beta.21", + "svelte": "^3.20.1" + } + }, + "typescript": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", + "dev": true + } + } +} diff --git a/libtests/typescript/package.json b/libtests/typescript/package.json new file mode 100644 index 00000000..b5bebc0d --- /dev/null +++ b/libtests/typescript/package.json @@ -0,0 +1,15 @@ +{ + "name": "pnotify-typescript-test", + "version": "0.0.0", + "description": "Just a test.", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "Apache-2.0", + "devDependencies": { + "@pnotify/core": "file:../../packages/core", + "typescript": "^3.8.3" + } +} diff --git a/libtests/typescript/tsconfig.json b/libtests/typescript/tsconfig.json new file mode 100644 index 00000000..be749e28 --- /dev/null +++ b/libtests/typescript/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "sourceMap": true, + "outDir": "dist", + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true + } +} diff --git a/packages/core/.npmignore b/packages/core/.npmignore new file mode 100644 index 00000000..eb8b4185 --- /dev/null +++ b/packages/core/.npmignore @@ -0,0 +1 @@ +cleanup.js diff --git a/packages/core/Stack.d.ts b/packages/core/Stack.d.ts new file mode 100644 index 00000000..1e45b156 --- /dev/null +++ b/packages/core/Stack.d.ts @@ -0,0 +1,193 @@ +import Notice from './'; + +declare abstract class StackProperties { + /** + * The primary stacking direction. Can be `'up'`, `'down'`, `'right'`, or + * `'left'`. + * + * @default null + */ + dir1: 'up' | 'down' | 'right' | 'left' | null; + /** + * The secondary stacking direction. Should be a perpendicular direction to + * `dir1`. The notices will continue in this direction when they reach the + * edge of the viewport along `dir1`. + * + * @default null + */ + dir2: 'up' | 'down' | 'right' | 'left' | null; + /** + * Number of pixels from the edge of the context, relative to `dir1`, the + * first notice will appear. If null, the current position of the notice, + * whatever that is, will be used. + * + * @default null + */ + firstpos1: number | null; + /** + * Number of pixels from the edge of the context, relative to `dir2`, the + * first notice will appear. If null, the current position of the notice, + * whatever that is, will be used. + * + * @default null + */ + firstpos2: number | null; + /** + * Number of pixels between notices along `dir1`. + * + * @default 25 + */ + spacing1: number; + /** + * Number of pixels between notices along `dir2`. + * + * @default 25 + */ + spacing2: number; + /** + * Where, in the stack, to push new notices. Can be `'top'` or `'bottom'`. + * + * @default 'bottom' + */ + push: 'top' | 'bottom'; + /** + * How many notices are allowed to be open in this stack at once. + * + * @default 1 + */ + maxOpen: number; + /** + * The strategy to use to ensure `maxOpen`. Can be `'wait'`, which will cause + * new notices to wait their turn, or `'close'`, which will remove the oldest + * notice to make room for a new one. + * + * @default 'wait' + */ + maxStrategy: 'wait' | 'close'; + /** + * Whether the notices that are closed to abide by `maxOpen` when + * `maxStrategy === 'close'` should wait and reopen in turn. + * + * @default true + */ + maxClosureCausesWait: boolean; + /** + * Whether the stack should be modal (`true`), modeless (`false`), or modalish + * (`'ish'`). Modalish stacks are cool. + * See https://sciactive.com/2020/02/11/the-modalish-notification-flow/. + * + * @default 'ish' + */ + modal: 'ish' | boolean; + /** + * Whether new notices that start waiting in a modalish stack should flash + * under the leader notice to show that they have been added. + * + * @default true + */ + modalishFlash: boolean; + /** + * Whether clicking on the modal overlay should close the stack's notices. + * + * @default true + */ + overlayClose: boolean; + /** + * Whether clicking on the modal to close notices also closes notices that + * have been pinned (`hide === false`). + * + * @default false + */ + overlayClosesPinned: boolean; + /** + * The DOM element this stack's notices should appear in. + * + * @default document.body + */ + context: HTMLElement; +} + +export type StackOptions = Partial; + +/** + * A stack is an instance of the `Stack` class used to determine where to + * position notices and how they interact with each other. + */ +export default class Stack extends StackProperties { + /** + * An "array" of notices. It's actually built on the fly from the double + * linked list the notices are actually stored in. + */ + readonly notices: Notice[]; + /** + * How many notices there are in the stack. + */ + readonly length: number; + /** + * When a stack is modalish, this is the notice that is open in the non-modal + * state. + */ + readonly leader: Notice | null; + + constructor(options: StackOptions); + + /** + * Run a callback for all the notices in the stack. `start` can be 'head', + * 'tail', 'oldest', or 'newest'. `dir` can be 'next', 'prev', 'older', or + * 'newer'. + * @param callback Function to run for each notice. + * @param options Controls which direction to iterate notices. + */ + forEach( + callback: (notice?: Notice) => void, + options?: { + /** + * Where to start the iteration. + * + * @default 'oldest' + */ + start: Notice | 'head' | 'tail' | 'oldest' | 'newest'; + /** + * Which direction in the double linked list to iterate. + * + * @default 'newer' + */ + dir: 'next' | 'prev' | 'newer' | 'older'; + /** + * Whether to skip notices whose open state is handled by a module. + * + * @default false + */ + skipModuleHandled: boolean; + } + ): void; + + /** + * Close all the notices in the stack. + * @param immediate Don't animate, just close immediately. + */ + close(immediate?: boolean): void; + + /** + * Open all the notices in the stack. + * @param immediate Don't animate, just open immediately. + */ + open(immediate?: boolean): void; + + /** + * Open the last closed/closing notice in the stack. + */ + openLast(): void; + + /** + * Position all the notices in the stack. + */ + position(): void; + + /** + * Queue a position call in that many milliseconds, unless another one is + * queued beforehand. + * @param milliseconds Time to wait before positioning. Default: 10. + */ + queuePosition(milliseconds?: number): void; +} diff --git a/packages/core/index.d.ts b/packages/core/index.d.ts new file mode 100644 index 00000000..4579102a --- /dev/null +++ b/packages/core/index.d.ts @@ -0,0 +1,358 @@ +import Stack, {StackProperties, StackOptions} from './Stack.js'; + +export as namespace PNotify; + +declare abstract class NoticeProperties { + /** + * Type of the notice. 'notice', 'info', 'success', or 'error'. + * + * @default 'notice' + */ + type: 'notice' | 'info' | 'success' | 'error'; + /** + * The notice's title. Can be a string, an element, or `false` for no title. + * + * @default false + */ + title: string | HTMLElement | false; + /** + * Whether to trust the title or escape its contents. (Not allow HTML.) + * + * @default false + */ + titleTrusted: boolean; + /** + * The notice's text. Can be a string, an element, or `false` for no text. + * + * @default false + */ + text: string | HTMLElement | false; + /** + * Whether to trust the text or escape its contents. (Not allow HTML.) + * + * @default false + */ + textTrusted: boolean; + /** + * What styling classes to use. (Can be 'brighttheme', 'material', another + * string provided by a module, or a styling object.) + * + * @default 'brighttheme' + */ + styling: string | {}; + /** + * What icons classes to use (Can be 'brighttheme', 'material', another string + * provided by a module, or an icon object.) + * + * @default 'brighttheme' + */ + icons: string | {}; + /** + * Light or dark version of the theme, if supported by the styling. This + * overrides the CSS media query when a preference is given. (Can be + * 'no-preference', 'light', or 'dark'.) + * + * @default 'no-preference' + */ + mode: 'no-preference' | 'light' | 'dark'; + /** + * Additional classes to be added to the notice. (For custom styling.) + * + * @default '' + */ + addClass: string; + /** + * Additional classes to be added to the notice, only when in modal. + * + * @default '' + */ + addModalClass: string; + /** + * Additional classes to be added to the notice, only when in modeless. + * + * @default '' + */ + addModelessClass: string; + /** + * Open the notice immediately when it is created. + * + * @default true + */ + autoOpen: boolean; + /** + * Width of the notice. + * + * @default '360px' + */ + width: string; + /** + * Minimum height of the notice. It will expand to fit content. + * + * @default '16px' + */ + minHeight: string; + /** + * Maximum height of the text container. If the text goes beyond this height, + * scrollbars will appear. Use null to remove this restriction. + * + * @default '200px' + */ + maxTextHeight: string | null; + /** + * Set icon to true to use the default icon for the selected style/type, false + * for no icon, or a string for your own icon class. + * + * @default true + */ + icon: boolean | string; + /** + * The animation to use when displaying and hiding the notice. 'none' and + * 'fade' are supported through CSS. Others are supported through the Animate + * module and Animate.css. + * + * @default 'fade' + */ + animation: string; + /** + * Speed at which the notice animates in and out. 'slow', 'normal', or 'fast'. + * Respectively, 400ms, 250ms, 100ms. + * + * @default 'normal' + */ + animateSpeed: 'slow' | 'normal' | 'fast'; + /** + * Display a drop shadow. + * + * @default true + */ + shadow: boolean; + /** + * After a delay, close the notice. + * + * @default true + */ + hide: boolean; + /** + * Delay in milliseconds before the notice is removed. If set to `Infinity`, + * the notice will not close, but it will not be considered sticky, so it will + * be closed along with all unstuck notices if the modal backdrop is clicked. + * + * @default 8000 + */ + delay: number; + /** + * Reset the hide timer if the mouse moves over the notice. + * + * @default true + */ + mouseReset: boolean; + /** + * Provide a button for the user to manually close the notice. + * + * @default true + */ + closer: boolean; + /** + * Only show the closer button on hover. + * + * @default true + */ + closerHover: boolean; + /** + * Provide a button for the user to manually stick the notice. + * + * @default true + */ + sticker: boolean; + /** + * Only show the sticker button on hover. + * + * @default true + */ + stickerHover: boolean; + /** + * The various displayed text, helps facilitating internationalization. + * + * @default {close: 'Close', stick: 'Pin', unstick: 'Unpin'} + */ + labels: { + close: string; + stick: string; + unstick: string; + }; + /** + * Remove the notice's elements from the DOM after it is closed. + * + * @default true + */ + remove: boolean; + /** + * Whether to remove the notice from the stack (and therefore, stack history) + * when it is closed. + * + * @default true + */ + destroy: boolean; + /** + * The stack on which the notices will be placed. Also controls the direction + * the notices stack. + * + * @default defaultStack + */ + stack: Stack; + /** + * This is where modules and their options should be added. It is a map of + * `module => options` entries. + * + * @default defaultModules + */ + modules: ModuleMap; +} + +interface Module {} +interface ModuleOptions {} + +type ModuleMap = Map; + +export type Options = Partial; + +export { Stack, StackProperties, StackOptions }; + +export abstract class Notice extends NoticeProperties { + refs: { + elem: HTMLDivElement | null; + container: HTMLDivElement | null; + content: HTMLDivElement | null; + iconContainer: HTMLDivElement | null; + titleContainer: HTMLDivElement | null; + textContainer: HTMLDivElement | null; + }; + + update(options: Options): void; + + /** + * Open the notice. + * @param immediate Don't animate, just open immediately. + */ + open(immediate?: boolean): void; + + /** + * Close the notice. + * @param immediate Don't animate, just close immediately. + * @param timerHide Used to determine whether the notice was closed by timing + * out. + * @param waitAfterward Set state to waiting after it's closed. + */ + close( + immediate?: boolean, + timerHide?: boolean, + waitAfterward?: boolean + ): void; + + /** + * Cancel any closing operation the notice is going through. + */ + cancelClose(): void; + + /** + * Queue the notice to close after the delay. + */ + queueClose(): void; + + /** + * Invokes the callback whenever the notice dispatches the event. Callback + * receives an `event` argument with a `detail` prop. Returns a function that + * removes the handler when invoked. + * @param eventType The name of the event to listen for. + * @param listener A callback to run when the event is fired. + * @returns A function that will remove the listener. + */ + on( + eventType: string, + listener: (event: Event & { detail: any }) => any + ): () => void; + + /** + * Fire an event. + * @param name The name of the event. + * @param detail The detail object to go on the event. + */ + fire(name: string, detail?: any): void; + + /** + * Returns the state of the notice. Can be 'waiting', 'opening', 'open', + * 'closing', or 'closed'. + */ + getState(): 'waiting' | 'open' | 'opening' | 'closed' | 'closing'; + + /** + * Run an attention getter animation from Animate.css. + * + * Provided by Animate module. + * + * @param aniClass The name of the animation class to use. + * @param callback A callback to run once the animation is done. + */ + attention(aniClass: string, callback?: () => any): void; +} + +declare interface DefaultStack extends Stack { + /** + * @default 'down' + */ + dir1: StackProperties['dir1']; + /** + * @default 'left' + */ + dir2: StackProperties['dir2']; + /** + * @default 25 + */ + firstpos1: StackProperties['firstpos1']; + /** + * @default 25 + */ + firstpos2: StackProperties['firstpos2']; + /** + * @default 36 + */ + spacing1: StackProperties['spacing1']; + /** + * @default 36 + */ + spacing2: StackProperties['spacing2']; + /** + * @default 'bottom' + */ + push: StackProperties['push']; +} + +export const defaultStack: DefaultStack; +export const defaultModules: ModuleMap; +export const defaults: NoticeProperties; + +/** + * Create a PNotify Notice. + * @param options Notice options. + */ +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' }; +/** + * Create a PNotify Notice with the type set to 'info'; + * @param options Notice options. + */ +export function info(options: Options | string): Notice & { type: 'info' }; +/** + * Create a PNotify Notice with the type set to 'success'; + * @param options Notice options. + */ +export function success(options: Options | string): Notice & { type: 'success' }; +/** + * Create a PNotify Notice with the type set to 'error'; + * @param options Notice options. + */ +export function error(options: Options | string): Notice & { type: 'error' }; diff --git a/packages/core/index.svelte b/packages/core/index.svelte index 69869bbe..28bbbaf9 100644 --- a/packages/core/index.svelte +++ b/packages/core/index.svelte @@ -58,88 +58,40 @@ export const defaultModules = new Map(); export const defaults = { - // Type of the notice. 'notice', 'info', 'success', or 'error'. type: 'notice', - // The notice's title. Can be a string, an element, or `false` for no title. title: false, - // Whether to trust the title or escape its contents. (Not allow HTML.) titleTrusted: false, - // The notice's text. Can be a string, an element, or `false` for no text. text: false, - // Whether to trust the text or escape its contents. (Not allow HTML.) textTrusted: false, - // What styling classes to use. (Can be 'brighttheme', 'material', or a - // styling object.) (Note that the Bootstrap modules provide a different - // default.) styling: 'brighttheme', - // What icons classes to use (Can be 'brighttheme', 'material', or an icon - // object.) (Note that the Font Awesome and Glyphicon modules provide a - // different default.) icons: 'brighttheme', - // Light or dark version of the theme, if supported by the styling. This - // overrides the CSS media query when a preference is given. (Can be - // 'no-preference', 'light', or 'dark'.) mode: 'no-preference', - // Additional classes to be added to the notice. (For custom styling.) addClass: '', - // Additional classes to be added to the notice, only when in modal. addModalClass: '', - // Additional classes to be added to the notice, only when in modeless. addModelessClass: '', - // Display the notice immediately when it is created. autoOpen: true, - // Width of the notice. width: '360px', - // Minimum height of the notice. It will expand to fit content. minHeight: '16px', - // Maximum height of the text container. If the text goes beyond this - // height, scrollbars will appear. Use null to remove this restriction. maxTextHeight: '200px', - // Set icon to true to use the default icon for the selected - // style/type, false for no icon, or a string for your own icon class. icon: true, - // The animation to use when displaying and hiding the notice. 'none' - // and 'fade' are supported through CSS. Others are supported - // through the Animate module and Animate.css. animation: 'fade', - // Speed at which the notice animates in and out. 'slow', 'normal', - // or 'fast'. Respectively, 400ms, 250ms, 100ms. animateSpeed: 'normal', - // Display a drop shadow. shadow: true, - // After a delay, remove the notice. hide: true, - // Delay in milliseconds before the notice is removed. If set to `Infinity`, - // the notice will not close, but it will not be considered sticky, so it - // will be closed along with all unstuck notices if the modal backdrop is - // clicked. delay: 8000, - // Reset the hide timer if the mouse moves over the notice. mouseReset: true, - // Provide a button for the user to manually close the notice. closer: true, - // Only show the closer button on hover. closerHover: true, - // Provide a button for the user to manually stick the notice. sticker: true, - // Only show the sticker button on hover. stickerHover: true, - // The various displayed text, helps facilitating internationalization. labels: { close: 'Close', stick: 'Pin', unstick: 'Unpin' }, - // Remove the notice's elements from the DOM after it is removed. remove: true, - // Whether to remove the notice from the stack (and therefore, stack - // history) when it is closed. destroy: true, - // The stack on which the notices will be placed. Also controls the - // direction the notices stack. stack: defaultStack, - // This is where modules and their options should be added. It is a map of - // `module => options` entries. modules: defaultModules }; diff --git a/packages/core/package.json b/packages/core/package.json index db62cd3d..5c320bd9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -37,6 +37,7 @@ }, "main": "dist/PNotify.js", "svelte": "index.svelte", + "types": "index.d.ts", "publishConfig": { "access": "public" },