diff --git a/CHANGELOG.md b/CHANGELOG.md index 024faae..25de607 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## next +- Added `@discoveryjs/discovery/utils` export - Renamed `Widget` class into `ViewModel` - Modified `PageRenderer#define()` and `ViewRenderer#define()` to allow specifying all options with a single config parameter: ```js diff --git a/package.json b/package.json index eefd5de..ef88975 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,10 @@ "discovery-dev": "./src/lib.js", "default": "./lib/lib.js" }, + "./utils": { + "discovery-dev": "./src/core/utils/index.js", + "default": "./lib/core/utils/index.js" + }, "./dist/*": "./dist/*", "./lib/*": "./lib/*", "./src/*": { diff --git a/src/core/utils/base64.ts b/src/core/utils/base64.ts index 27d590d..d24a9cc 100644 --- a/src/core/utils/base64.ts +++ b/src/core/utils/base64.ts @@ -57,10 +57,10 @@ export function encodeBytes(input: string | number[] | Uint8Array): Uint8Array { } export function decode(input: string): string { - return decoder.decode(decodeBase64Bytes(input)); + return decoder.decode(decodeBytes(input)); } -export function decodeBase64Bytes(input: string): Uint8Array { +export function decodeBytes(input: string): Uint8Array { let inputSize = input.length; // ignore trailing "=" (padding) diff --git a/src/core/utils/compare.ts b/src/core/utils/compare.ts index 5bd5bab..46a232a 100644 --- a/src/core/utils/compare.ts +++ b/src/core/utils/compare.ts @@ -7,7 +7,7 @@ export function equal(a: any, b: any, compare = Object.is) { return true; } - if (!a || typeof a !== 'object' || !b || typeof b !== 'object') { + if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) { return false; } diff --git a/src/core/utils/container-styles.ts b/src/core/utils/container-styles.ts index 786711f..4bc4799 100644 --- a/src/core/utils/container-styles.ts +++ b/src/core/utils/container-styles.ts @@ -2,7 +2,7 @@ import { InitValue, resolveDarkmodeValue } from '../darkmode.js'; type Styles = Record; type SavedStyles = Record; -type ApplyContainerStylesOptions = { +export type ApplyContainerStylesOptions = { darkmode?: InitValue, darkmodePersistent?: boolean; }; diff --git a/src/core/utils/debounce.ts b/src/core/utils/debounce.ts index 70862da..5247a69 100644 --- a/src/core/utils/debounce.ts +++ b/src/core/utils/debounce.ts @@ -4,12 +4,12 @@ * Adopted for Discovery.js project */ -type Options = { +export type DebounceOptions = { wait: number; maxWait?: number; leading?: boolean; } -type DebounceMethods = { +export type DebounceMethods = { cancel(): void; flush(): R; pending(): boolean; @@ -85,7 +85,7 @@ export function debounce< T extends (...args: A) => R, A extends any[], R ->(func: T, options?: Options | number): T & DebounceMethods { +>(func: T, options?: DebounceOptions | number): T & DebounceMethods { if (typeof func !== 'function') { throw new TypeError('Expected a function'); } diff --git a/src/core/utils/dom.ts b/src/core/utils/dom.ts index 9492728..bb1d5c1 100644 --- a/src/core/utils/dom.ts +++ b/src/core/utils/dom.ts @@ -1,19 +1,18 @@ /* eslint-env browser */ import { hasOwn } from './object-utils.js'; -type EventHandler = (this: Element, evt: Event) => void; -type Attrs = { - [key in keyof HTMLElementEventMap as `on${key}`]?: EventHandler< - HTMLElementTagNameMap[TagName], - HTMLElementEventMap[key] - >; +export type CreateElementAttrs = { + [key in keyof HTMLElementEventMap as `on${key}`]?: ( + this: HTMLElementTagNameMap[TagName], + evt: HTMLElementEventMap[key] + ) => void; } & { [key: string]: any | undefined; // TODO: replace "any" with "string" }; export function createElement( tag: TagName, - attrs?: Attrs | string | null, + attrs?: CreateElementAttrs | string | null, children?: (Node | string)[] | string ) { const el = document.createElement(tag); diff --git a/src/core/utils/index.js b/src/core/utils/index.js deleted file mode 100644 index 687f7f4..0000000 --- a/src/core/utils/index.js +++ /dev/null @@ -1,41 +0,0 @@ -import * as containerStyles from './container-styles.js'; -import * as base64 from './base64.js'; -import * as compare from './compare.js'; -import { copyText } from './copy-text.js'; -import { debounce } from './debounce.js'; -import * as dom from './dom.js'; -import * as html from './html.js'; -import { injectStyles } from './inject-styles.js'; -import * as types from './is-type.js'; -import * as json from './json.js'; -import * as layout from './layout.js'; -import * as dataLoad from './load-data.js'; -import * as objectUtils from './object-utils.js'; -import * as pattern from './pattern.js'; -import * as persistent from './persistent.js'; -import * as pointer from './pointer.js'; -import { Progressbar } from './progressbar.js'; -import { safeFilterRx } from './safe-filter-rx.js'; -import * as size from './size.js'; - -export default { - ...containerStyles, - base64, - ...compare, - copyText, - ...dataLoad, - debounce, - ...dom, - ...html, - injectStyles, - ...types, - ...objectUtils, - ...json, - ...layout, - pattern, - persistent, - ...pointer, - Progressbar, - safeFilterRx, - ...size -}; diff --git a/src/core/utils/index.ts b/src/core/utils/index.ts new file mode 100644 index 0000000..62e3938 --- /dev/null +++ b/src/core/utils/index.ts @@ -0,0 +1,26 @@ +export * as base64 from './base64.js'; +export { equal, deepEqual } from './compare.js'; +export type { ApplyContainerStylesOptions } from './container-styles.js'; +export { applyContainerStyles, rollbackContainerStyles } from './container-styles.js'; +export { copyText } from './copy-text.js'; +export { debounce } from './debounce.js'; +export type { CreateElementAttrs } from './dom.js'; +export { createElement, createFragment, createText, isDocumentFragment, passiveCaptureOptions, passiveSupported } from './dom.js'; +export { randomId } from './id.js'; +export { escapeHtml, numDelim } from './html.js'; +export type { InjectStyle, InjectInlineStyle, InjectLinkStyle } from './inject-styles.js'; +export { injectStyles } from './inject-styles.js'; +export type { TypedArray } from './is-type.js'; +export { isTypedArray, isArray, isSet, isRegExp } from './is-type.js'; +export { jsonStringifyAsJavaScript, jsonStringifyInfo } from './json.js'; +export { getBoundingRect, getOffsetParent, getOverflowParent, getPageOffset, getViewportRect } from './layout.js'; +export type * from './load-data.js'; +export * from './load-data.js'; +export { objectToString, hasOwn } from './object-utils.js'; +export { match, matchAll } from './pattern.js'; +export { localStorageEntry, sessionStorageEntry } from './persistent.js'; +export { pointerXY } from './pointer.js'; +export type * from './progressbar.js'; +export { Progressbar } from './progressbar.js'; +export { safeFilterRx } from './safe-filter-rx.js'; +export { ContentRect } from './size.js'; diff --git a/src/core/utils/inject-styles.ts b/src/core/utils/inject-styles.ts index 6224399..623d1ec 100644 --- a/src/core/utils/inject-styles.ts +++ b/src/core/utils/inject-styles.ts @@ -1,18 +1,18 @@ import { createElement } from './dom.js'; -export type Style = string | InlineStyle | LinkStyle; -export type InlineStyle = { +export type InjectStyle = string | InjectInlineStyle | InjectLinkStyle; +export type InjectInlineStyle = { type: 'style' | 'inline'; content: string; media?: string; }; -export type LinkStyle = { +export type InjectLinkStyle = { type: 'link' | 'external'; href: string; media?: string; }; -export async function injectStyles(el: HTMLElement | ShadowRoot, styles?: Style[]) { +export async function injectStyles(el: HTMLElement | ShadowRoot, styles?: InjectStyle[]) { const foucFix = createElement('style', null, ':host{display:none}'); const awaitingStyles = new Set>(); diff --git a/src/core/utils/is-type.ts b/src/core/utils/is-type.ts index c526f73..f453353 100644 --- a/src/core/utils/is-type.ts +++ b/src/core/utils/is-type.ts @@ -1,6 +1,6 @@ import { objectToString } from './object-utils.js'; -type TypedArray = +export type TypedArray = | Uint8Array | Uint8ClampedArray | Uint16Array diff --git a/src/core/utils/layout.ts b/src/core/utils/layout.ts index 2d79258..8b6a378 100644 --- a/src/core/utils/layout.ts +++ b/src/core/utils/layout.ts @@ -1,6 +1,6 @@ /* eslint-env browser */ const { documentElement } = document; -const standartsMode = document.compatMode === 'CSS1Compat'; +const standardsMode = document.compatMode === 'CSS1Compat'; export function getOffsetParent(node: HTMLElement) { let offsetParent = node.offsetParent as HTMLElement; @@ -42,7 +42,7 @@ export function getPageOffset(element: HTMLElement | null = null) { left = -rect.left; } else { // offset relative to page - if (standartsMode) { + if (standardsMode) { top = window.pageYOffset || documentElement.scrollTop; left = window.pageXOffset || documentElement.scrollLeft; } else { @@ -90,7 +90,7 @@ export function getViewportRect( element: HTMLElement | Window, relElement: HTMLElement | null = null ) { - const topViewport = standartsMode ? document.documentElement : document.body; + const topViewport = standardsMode ? document.documentElement : document.body; let { top, left } = element === topViewport && !relElement ? getPageOffset() : getBoundingRect(element, relElement); diff --git a/src/core/utils/pattern.ts b/src/core/utils/pattern.ts index 593f9c7..a594f1c 100644 --- a/src/core/utils/pattern.ts +++ b/src/core/utils/pattern.ts @@ -12,7 +12,7 @@ function matchWithString(str: string, pattern: string, lastIndex: number) { return offset !== -1 ? { offset, length: pattern.length } : null; }; -export function has(text: string, pattern: RegExp | string | null, ignoreCase = false) { +export function match(text: string, pattern: RegExp | string | null, ignoreCase = false) { if (isRegExp(pattern)) { return ignoreCase && !pattern.ignoreCase ? new RegExp(pattern, pattern.flags + 'i').test(text) diff --git a/src/core/utils/persistent.ts b/src/core/utils/persistent.ts index 5c63591..38c58d0 100644 --- a/src/core/utils/persistent.ts +++ b/src/core/utils/persistent.ts @@ -1,6 +1,10 @@ import { Emitter } from '../emitter.js'; export type StorageType = 'localStorage' | 'sessionStorage'; +export type StorageMap = Map & { + storage: Storage | null; + getOrCreate: PersistentKeyGetOrCreate; +}; export type PersistentValue = string | null; export type PersistentKey = { readonly value: PersistentValue, @@ -15,11 +19,7 @@ export type PersistentKeyEvents = { change: [value: PersistentValue]; foo: []; } -export type StorageMap = Map & { - storage: Storage | null; - getOrCreate: GetOrCreate; -}; -export type GetOrCreate = ((key: string) => PersistentKey) & { +export type PersistentKeyGetOrCreate = ((key: string) => PersistentKey) & { available: boolean; }; diff --git a/src/core/utils/progressbar.ts b/src/core/utils/progressbar.ts index 1de6e4d..7f345b6 100644 --- a/src/core/utils/progressbar.ts +++ b/src/core/utils/progressbar.ts @@ -1,21 +1,21 @@ import { Observer } from '../observer.js'; import { createElement } from './dom.js'; -export type Stage = keyof typeof loadStages; -export type Timing = { - stage: Stage; +export type ProgressbarStage = keyof typeof loadStages; +export type ProgressbarTiming = { + stage: ProgressbarStage; title: string; duration: number; }; -export type OnTimingCallback = (timing: Timing) => void; -export type OnFinishCallback = (timings: Timing[] & { awaitRepaintPenaltyTime: number }) => void; +export type ProgressbarOnTimingCallback = (timing: ProgressbarTiming) => void; +export type ProgressbarOnFinishCallback = (timings: ProgressbarTiming[] & { awaitRepaintPenaltyTime: number }) => void; export type ProgressbarOptions = Partial<{ - onTiming: OnTimingCallback; - onFinish: OnFinishCallback; + onTiming: ProgressbarOnTimingCallback; + onFinish: ProgressbarOnFinishCallback; domReady: Promise; }>; export type ProgressbarState = { - stage: Stage; + stage: ProgressbarStage; progress: { done: boolean; elapsed: number; @@ -86,7 +86,7 @@ const letRepaintIfNeeded = async () => { } }; -export function decodeStageProgress(stage: Stage, progress: ProgressbarState['progress'], step?: string) { +export function decodeStageProgress(stage: ProgressbarStage, progress: ProgressbarState['progress'], step?: string) { const { value, title: stageTitle, duration } = loadStages[stage]; let progressValue = 0; let progressText: string | null = null; @@ -132,9 +132,9 @@ export class Progressbar extends Observer { awaitRepaintPenaltyTime: number; finished: boolean; awaitRepaint: number | null; - timings: Timing[]; - onTiming: OnTimingCallback; - onFinish: OnFinishCallback; + timings: ProgressbarTiming[]; + onTiming: ProgressbarOnTimingCallback; + onFinish: ProgressbarOnFinishCallback; appearanceDelay: number; domReady: Promise; el: HTMLElement; @@ -163,8 +163,8 @@ export class Progressbar extends Observer { ]); } - recordTiming(stage: Stage, start: number, end = performance.now()) { - const entry: Timing = { + recordTiming(stage: ProgressbarStage, start: number, end = performance.now()) { + const entry: ProgressbarTiming = { stage, title: loadStages[stage].title, duration: int(end - start) diff --git a/src/core/utils/size.ts b/src/core/utils/size.ts index ce331dc..f634b4c 100644 --- a/src/core/utils/size.ts +++ b/src/core/utils/size.ts @@ -3,6 +3,8 @@ import { Observer } from '../observer.js'; const resizeObserverSupported = typeof ResizeObserver === 'function'; export class ContentRect extends Observer { + static supported = resizeObserverSupported; + private el: HTMLElement | null; private observer: ResizeObserver | null; diff --git a/src/lib.ts b/src/lib.ts index 92f7d55..d2a6758 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -8,7 +8,7 @@ import router from './extensions/router.js'; import embed from './extensions/embed-client.js'; import { buttons as navButtons } from './nav/index.js'; import { encoding as jsonxl } from './core/encodings/jsonxl.js'; -import utils from './core/utils/index.js'; +import * as utils from './core/utils/index.js'; export type * from './main/index.js'; export { diff --git a/src/main/app.ts b/src/main/app.ts index 0abfb3b..b82f4f7 100644 --- a/src/main/app.ts +++ b/src/main/app.ts @@ -2,7 +2,7 @@ import type { SetDataProgressOptions, ViewModelEvents, ViewModelOptions } from './view-model.js'; import type { LoadDataBaseOptions, LoadDataResult } from '../core/utils/load-data.js'; -import type { Style } from '../core/utils/inject-styles.js'; +import type { InjectStyle } from '../core/utils/inject-styles.js'; import type { ProgressbarOptions } from '../core/utils/progressbar.js'; import type { UploadOptions } from '../extensions/upload.js'; import { hasOwn } from '../core/utils/object-utils.js'; @@ -222,7 +222,7 @@ export class App< super.unloadData(); } - initDom(styles?: Style[]) { + initDom(styles?: InjectStyle[]) { super.initDom(styles); this.dom.container.append( diff --git a/src/main/view-model.ts b/src/main/view-model.ts index 9c37616..5646f65 100644 --- a/src/main/view-model.ts +++ b/src/main/view-model.ts @@ -2,7 +2,7 @@ import type { ModelEvents, ModelOptions, PageParams, PageRef, SetDataOptions } from './model.js'; import type { Dataset } from '../core/utils/load-data.js'; -import type { Style } from '../core/utils/inject-styles.js'; +import type { InjectStyle } from '../core/utils/inject-styles.js'; import type { PageOptionName, PageOptions } from '../core/page.js'; import type { SingleViewConfig } from '../core/view.js'; import type { Progressbar } from '../core/utils/progressbar.js'; @@ -63,7 +63,7 @@ export interface ViewModelEvents extends ModelEvents { } export interface ViewModelOptions extends ModelOptions { container: HTMLElement; - styles: Style[]; + styles: InjectStyle[]; compact: boolean; darkmode: InitValue; @@ -324,7 +324,7 @@ export class ViewModel< // UI // - initDom(styles?: Style[]) { + initDom(styles?: InjectStyle[]) { const wrapper = createElement('div', 'discovery'); const shadow = wrapper.attachShadow({ mode: 'open' }); const readyStyles = injectStyles(shadow, styles); diff --git a/src/preloader.ts b/src/preloader.ts index cc409f6..bea82ce 100644 --- a/src/preloader.ts +++ b/src/preloader.ts @@ -1,5 +1,5 @@ import type { InitValue } from './core/darkmode.js'; -import type { Style } from './core/utils/inject-styles.js'; +import type { InjectStyle } from './core/utils/inject-styles.js'; import type { LoadDataBaseOptions, LoadDataFetchOptions, LoadDataResult } from './core/utils/load-data.js'; import { hasOwn } from './core/utils/object-utils.js'; import { randomId } from './core/utils/id.js'; @@ -12,7 +12,7 @@ type PushDataLoading = ReturnType; export type PreloaderOptions = { dataSource: keyof typeof dataSource; container: HTMLElement; - styles: Style[]; + styles: InjectStyle[]; darkmode: InitValue; darkmodePersistent: boolean; embed: boolean;