shark-mvc
Version:
Shark-MVC is a small MVC framework born to make our small JS apps easier to build.
1,089 lines (875 loc) • 43.5 kB
TypeScript
// ── Shared interfaces ─────────────────────────────────────────────────────────
/** Describes a single auto-listener entry stored in `__listeners`. */
interface JawListenerEntry {
context?: string;
handler: ((...args: any[]) => any) | string;
originalHandler?: string;
target: "DOM" | "controller" | "component";
type: "autoListener";
smartUnlisten?: boolean;
}
/** Describes an item used in array-form calls to `set()`. */
interface JawSetItem {
name: string;
value: any;
}
/** Describes an entry in the `__validMethodList` built by `_extractValidMethodList`. */
interface JawValidMethod {
handlerName: string;
handlerHolder: Jaw;
_from?: string;
depthList?: number[];
minDepth?: number;
targetType?: "singleNode" | "multiNode" | "manager";
}
// ── Jaw ───────────────────────────────────────────────────────────────────────
/**
* Represents an element of the MVC model (View or Controller).
* Extends the native `EventTarget` and adds Shark-specific methods.
*/
export declare class Jaw extends EventTarget {
/** Read-only identifier for this Jaw instance. Set from `jawId` in the constructor. */
readonly className: string;
/** Read-only identifier alias for `className`. */
readonly id: string;
/** If the routing method is set to "url", this will be the URL displayed when this Jaw is active. */
route: string | null;
/** Whether this Jaw has been initialized via `init()`. */
isInitialized: boolean;
/** The options object used in the most recent open operation. */
currentOpenOptions: Record<string, any>;
/**
* Reactive proxy over `__store`. Properties defined here trigger auto-render when changed,
* if `Shark.settings.enableJawStoreAutoRender` is enabled.
*/
$store: Record<string, any>;
/** @internal Internal data store, backing `$store` and `get()`/`set()`. */
protected __store: Record<string, any>;
/** @internal Fields registered for auto-rendering. */
protected __renderFields: string[];
/** @internal Map of child templates used during rendering. */
protected __childTemplates: Record<string, string>;
/** @internal Map of dynamic data bindings. */
protected __dynamicDataBindings: Record<string, any>;
/** @internal Registry of event listeners assigned by `smartListen`. */
protected __listeners: Record<string, Record<string, JawListenerEntry>>;
/** @internal Named methods added to this Jaw. */
protected __methods: Record<string, any>;
/** @internal View binding map (primary). */
protected __viewBinds: Record<string, any>;
/** @internal View binding map (secondary). */
protected __viewBinds2: Record<string, any>;
/** @internal Hash map used for change-detection in `__singleSet`. */
protected __storeHashMap: Map<string, any>;
/** @internal List of valid handler methods built by `smartListen`. */
protected __validMethodList?: JawValidMethod[];
/**
* The list of Components currently active on this Jaw.
* Use `addComponent` / `addComponents` to modify it.
*/
readonly activeComponents: Jaw[];
/**
* Creates a new Jaw instance.
*
* @param jawId - The identifier for this Jaw. If omitted/null/empty, defaults to the
* camelCase class name of the subclass.
*/
constructor(jawId?: string | null);
/**
* @deprecated Pass only `jawId`. The `jawDefinition` parameter will be removed in a future version.
*/
// eslint-disable-next-line @typescript-eslint/unified-signatures
constructor(jawId: string | null | undefined, jawDefinition: object);
/**
* Add a single Component to this Jaw.
*
* @param component - The Component to add, either as a string ID or a Component instance.
* Passing a string is deprecated; prefer passing the instance directly.
* @returns The updated list of active Components.
*/
addComponent(component: Jaw | string): Jaw[];
/**
* Add multiple Components to this Jaw.
*
* @param components - Array of Components to add (as instances or string IDs).
* @returns The updated list of active Components.
*/
addComponents(components: (Jaw | string)[]): Jaw[];
/**
* Retrieve one or more values from the internal data store.
*
* - Called with no arguments: returns the entire `__store` object.
* - Called with a string: returns the value for that key.
* - Called with an array of strings: returns an array of the corresponding values.
*/
get(itemName?: string): any;
get(itemName: string[]): any[];
get(itemName: undefined): Record<string, any>;
/** Called when this Jaw is used for the first time. Override in subclasses. */
init(): void;
/** @internal Extracts valid `*Handler` methods from a list of Components, recursively. */
protected _extractComponentsValidMethodList(
componentList: Jaw[],
processedComponentList: string[],
notProcessedComponentList: string[]
): JawValidMethod[];
/** @internal Extracts valid `*Handler` methods from a target object and its prototype chain. */
protected _extractValidMethodList(targetObject: Jaw): JawValidMethod[];
/** @internal Initialises Components, calling `init()` on each if not already done. */
protected _initComponents(componentList?: Jaw[] | null, processedComponentList?: string[]): void;
/**
* Store one or more values in this Jaw's internal data store.
*
* Accepted signatures:
* - `set(name, value)` — set a single named value.
* - `set(object)` — merge all properties of the object into the store.
* - `set(array)` — each item must have `{ name, value }`.
*/
set(nameOrItems: string, value: any): void;
set(nameOrItems: Record<string, any>): void;
set(nameOrItems: JawSetItem[]): void;
/** @internal Sets a single key on the store, returns `1` if the value changed, `0` otherwise. */
protected __singleSet(name: string, value: any): 0 | 1;
/**
* Dispatch an event from this Jaw using jQuery's `.trigger()`.
*/
emit(eventName: string, eventData?: any): JQuery;
/**
* Remove an event listener from this Jaw using jQuery's `.off()`.
*/
off(eventName: string, eventHandler: (...args: any[]) => any): JQuery;
/**
* Attach an event listener to this Jaw using jQuery's `.on()`.
*/
on(eventName: string, eventHandler: (...args: any[]) => any): JQuery;
/**
* Automatically match and assign DOM and controller event listeners based on the
* naming convention `selectorOrControllerName_eventNameHandler`.
*
* @param container - Optional CSS selector or jQuery selection to restrict parsing scope.
* @param selfAssign - If `true`, the container itself is treated as a potential target.
*/
smartListen(container?: string | JQuery, selfAssign?: boolean): void;
/** @internal Experimental v2 of `smartListen`. Work in progress. */
smartListen2(container?: string | JQuery, selfAssign?: boolean): void;
/**
* Remove all listeners previously assigned by `smartListen`.
*/
smartUnlisten(container?: string | JQuery): void;
/**
* Internal tracing utility. Delegates to `Shark.trace` if available.
*/
trace(message: any, level?: string): void;
/**
* Update the DOM portions bound to a reference model path after the model's dirty
* properties have changed.
*/
updateDisplay(referenceModelPath: string, sourceEvent?: Event): void;
/** @internal Utility to compute the DOM depth of a node (distance from BODY). */
static __getNodeDepth(node: Node): number;
}
// ── RenderableJaw ─────────────────────────────────────────────────────────────
/**
* The base class for View, Component and PageTemplate.
* Adds template management on top of Jaw.
*/
export declare class RenderableJaw extends Jaw {
/** The default template HTML string (raw, before parsing). */
templateContent: string;
/** The type identifier for this class (overridden in subclasses). */
type: string;
/**
* Map of all registered templates, keyed by name.
* Each entry contains `{ content, parsed, parsedContent }`.
* The default template is stored under the key `"__default__"`.
* Non-configurable, non-writable (contents are mutable).
*/
readonly templates: Record<string, { content: string; parsed: boolean; parsedContent: string }>;
/**
* Map of fully-expanded template strings keyed by name.
* Populated by `parseTemplates()`. The default template is under `"__default__"`.
* Non-configurable, non-writable (contents are mutable).
*/
readonly parsedTemplates: Record<string, string>;
/**
* The parsed default template string.
* @deprecated Prefer `parsedTemplates["__default__"]`. Kept for back-compat.
*/
protected __parsedTemplate: string | null;
/**
* `true` after `parseTemplates()` has run at least once.
* @deprecated Prefer checking `templates["__default__"].parsed`. Kept for back-compat.
*/
protected __templatesParsed?: boolean;
/** @internal Raw template map (legacy). Prefer `templates`. */
protected __templates: Record<string, string>;
constructor(jawId?: string | null);
/** @deprecated Pass only `jawId`. */
// eslint-disable-next-line @typescript-eslint/unified-signatures
constructor(jawId: string | null | undefined, jawDefinition: object);
/**
* Add a named HTML template to this RenderableJaw.
*
* @param name - Template name. Pass `""` to register the default template (only one allowed).
* Cannot be `"__default__"` (reserved) or contain spaces.
* @param content - The raw HTML template string.
* @returns The internal `__templates` map, or `undefined` if validation failed.
*/
addTemplate(name: string, content: string): Record<string, string> | undefined;
/**
* Set the default HTML template for this RenderableJaw.
* Shorthand for `addTemplate("", content)`.
*
* @param content - The raw HTML template string.
* @returns The internal `__templates` map, or `undefined` if validation failed.
*/
setTemplate(content: string): Record<string, string> | undefined;
/**
* Parse all registered templates, resolving:
* - PageTemplate slot injection (`<shark-template>`)
* - Component expansion (`<shark-xxx-component>`) including named slot content and `prop-*` attributes
* - `data-sh-model` → `data-shguid` binding
* - Auto-bound Shark `$store` properties
* - `SmartModel` bound properties
* - Dynamic sections (`<shark-section>`)
* - `[[renderer.field]]` syntax
*
* This is a no-op if the template has already been parsed, unless `force: true` is passed.
* Called automatically by `PreactRenderer.render()` before the first render.
*
* @param options.force - If `true`, re-parses even already-parsed templates. Default: `false`.
* @param options.sharkModelGuidPlaceHolder - Placeholder for `data-shguid` on Mustache model refs.
* Default: `"__ph__"`.
*/
parseTemplates(options?: { force?: boolean; sharkModelGuidPlaceHolder?: string }): void;
}
// ── Component ─────────────────────────────────────────────────────────────────
/**
* The class to extend to create Component classes.
* Components are sub-parts of a View, used to render and manage portions of the UI.
*
* A Component's HTML template can contain:
* - `<slot name="..."></slot>` placeholders, filled at the call site with named slot content.
* - `{{props.xxx}}` placeholders, filled with values passed via `prop-xxx="value"` attributes.
*/
export declare class Component extends RenderableJaw {
/** Always `"component"` — non-writable and non-configurable. */
readonly type: "component";
/**
* Props passed to this component instance at the call site via `prop-*` attributes.
* Populated at render time; writable.
*/
props: Record<string, any>;
/**
* Creates a new Component instance and automatically registers it with Shark
* via `Shark.registerComponent()`.
*
* @param jawId - Identifier for this Component. Defaults to the camelCase subclass name.
*/
constructor(jawId?: string | null);
/** @deprecated Pass only `jawId`. */
// eslint-disable-next-line @typescript-eslint/unified-signatures
constructor(jawId: string | null | undefined, jawDefinition: object);
}
// ── Controller ────────────────────────────────────────────────────────────────
/**
* The class to extend to create Controller classes.
* Controllers manage application logic and are registered globally with Shark.
*/
export declare class Controller extends Jaw {
/** Always `"controller"` — non-writable and non-configurable. */
readonly type: "controller";
/**
* Creates a new Controller instance and automatically registers it with Shark
* via `Shark.registerController()`.
*
* @param jawId - Identifier for this Controller. Defaults to the camelCase subclass name.
*/
constructor(jawId?: string | null);
/** @deprecated Pass only `jawId`. */
// eslint-disable-next-line @typescript-eslint/unified-signatures
constructor(jawId: string | null | undefined, jawDefinition: object);
}
// ── PageTemplate ──────────────────────────────────────────────────────────────
/**
* The class to extend to create PageTemplate classes.
* A PageTemplate defines a shared layout shell with named `<slot>` placeholders
* that individual Views fill via `<shark-template name="..." slot="...">`.
*
* PageTemplates can only have a single template — use `setTemplate()`.
* Calling `addTemplate()` throws an error.
*/
export declare class PageTemplate extends RenderableJaw {
/** Always `"pageTemplate"` — non-writable and non-configurable. */
readonly type: "pageTemplate";
/**
* Creates a new PageTemplate instance.
*
* @param jawId - Identifier for this PageTemplate. Defaults to the camelCase subclass name.
*/
constructor(jawId?: string | null);
/**
* @throws Always throws — PageTemplates support only one template. Use `setTemplate()`.
*/
addTemplate(name: string, content: string): never;
}
// ── View ──────────────────────────────────────────────────────────────────────
/**
* The class to extend to create View classes.
* A View can only have a single template; use `setTemplate()` to set it.
*/
export declare class View extends RenderableJaw {
/**
* The main route associated with this View.
* @deprecated In favour of the SwimWay router.
*/
$mainRoute: string;
/** Always `"view"` — non-writable and non-configurable. */
readonly type: "view";
/**
* Creates a new View instance and automatically registers it with Shark
* via `Shark.registerView()`.
*
* @param jawId - Identifier for this View. Defaults to the camelCase subclass name.
*/
constructor(jawId?: string | null);
/** @deprecated Pass only `jawId`. */
// eslint-disable-next-line @typescript-eslint/unified-signatures
constructor(jawId: string | null | undefined, jawDefinition: object);
/**
* @deprecated Views support only one template. Use `setTemplate()` instead.
* This override ignores `name` and delegates to `setTemplate(content)`.
*/
addTemplate(name: string, content: string): Record<string, string> | undefined;
}
// ── Shark ─────────────────────────────────────────────────────────────────────
export declare class Shark {
// ── Trace level constants ─────────────────────────────────────────────────
static readonly TRACE_ERROR: "error";
static readonly TRACE_INFO: "info";
static readonly TRACE_LOG: "log";
static readonly TRACE_NONE: "none";
static readonly TRACE_WARN: "warn";
// ── Global state ──────────────────────────────────────────────────────────
/** The global reactive store. Properties set here trigger auto-render on bound Views. */
static $store: Record<string, any>;
/** The currently active page descriptor. */
static activePage: { id: string };
/** Application-level data bag. */
static appData: Record<string, any>;
/** Whether Shark has been initialized. */
static isInitialized: boolean;
/** Localization labels. */
static labels: Record<string, any>;
/** Registered page templates, keyed by name. */
static pageTemplates: Record<string, PageTemplate>;
/** Current trace level. Defaults to `TRACE_WARN`. */
static traceLevel: string;
// ── Lifecycle ─────────────────────────────────────────────────────────────
/**
* Initialize the Shark framework.
*
* @param options.labels - Localization labels object.
* @param options.remoteCallTimeOutDuration - Milliseconds before aborting remote calls. Default: 15000.
* @param options.templateRenderer - Renderer class to use. Default: PreactRenderer.
* @param forceReinit - If `true`, re-runs initialization even if already done.
*/
static init(options?: {
labels?: Record<string, any>;
remoteCallTimeOutDuration?: number;
templateRenderer?: any;
[key: string]: any;
}, forceReinit?: boolean): void;
/**
* Render the given jaw (or the current active View if omitted) to the screen.
*
* @param currJaw - The jaw to render. Defaults to the currently active View.
* @param options - Renderer options passed through to the active renderer.
*/
static render(currJaw?: Jaw, options?: Record<string, any>): boolean;
// ── Navigation ────────────────────────────────────────────────────────────
/**
* Open a registered View, making it the active page.
*
* @param pageIdOrJaw - The View instance or its string ID.
* @param options - Options forwarded to the View's `open()` / `init()` lifecycle.
*/
static openPage(pageIdOrJaw: string | Jaw, options?: Record<string, any>): boolean;
/**
* Activate a View without necessarily making it the visible page.
*/
static activateView(pageIdOrJaw: string | Jaw, options?: Record<string, any>): boolean;
/**
* Navigate to a SwimWay route.
*
* @param route - The route string or SwimRoute object to navigate to.
*/
static navigateToRoute(route: string | object): void;
/**
* Parse the current `document.location` and return the View name encoded in the URL.
*/
static parseRequestedViewFromLocation(): string;
// ── Store ─────────────────────────────────────────────────────────────────
/**
* Retrieve one or more values from the global store.
*
* @param itemName - Key name or array of key names. If omitted, returns the whole store.
*/
static get(itemName?: string | string[]): any;
/**
* Set one or more values on the global store.
* Triggers auto-render on any Views that reference the changed keys.
*
* @param nameOrObject - Property name or plain object of `{ name: value }` pairs.
* @param value - Value to assign (only used when `nameOrObject` is a string).
*/
static set(nameOrObject: string | Record<string, any>, value?: any): any;
// ── Registration ──────────────────────────────────────────────────────────
/** Register a Component so it can be referenced in templates. */
static registerComponent(jaw: Component): void;
/** Register a Controller. */
static registerController(jaw: Controller): void;
/** Register a SmartModel class under a given name. */
static registerModel(modelName: string, modelClass: typeof SmartModel): void;
/**
* Register a PageTemplate so Views can reference it by name.
* Call this after `setTemplate()` on the PageTemplate instance.
*/
static registerPageTemplate(jaw: PageTemplate): void;
/** Register a View so it can be opened via `openPage()`. */
static registerView(jaw: View): void;
// ── Model helpers ─────────────────────────────────────────────────────────
/**
* Return a new instance of a registered model, optionally pre-populated with data.
*
* @param modelNameOrClass - The registered name string or the model class itself.
* @param modelData - Data to import into the new instance.
*/
static getInstance(modelNameOrClass: string | typeof SmartModel, modelData?: object): SmartModel;
/**
* Return an array of model instances from an array of data objects.
*/
static getInstanceList(modelNameOrClass: string | typeof SmartModel, dataList: object[]): SmartModel[];
/**
* Retrieve a model instance registered in the global store by dot-notation path.
*/
static getReferenceModel(path: string): SmartModel | null;
// ── Utilities ─────────────────────────────────────────────────────────────
/** Bind a View's DOM to a model using a CSS container selector. */
static bindViewToModel(containerCSSSelector: string): void;
/** Generate a UUID string. */
static generateUUID(): string;
/**
* Emit a trace message if the current `traceLevel` allows it.
*
* @param args - Message and optional data to log.
*/
static trace(...args: any[]): void;
/**
* Return `true` if the given level should be emitted at the current `traceLevel`.
*/
static shouldTrace(level: string): boolean;
/**
* Update URLSearchParams in the current URL without adding a browser history entry.
*/
static setURLSearchParams(
urlSearchParams: string | object | URLSearchParams | null,
replace?: boolean,
routeObject?: object | null
): void;
}
// ── SmartModel ────────────────────────────────────────────────────────────────
/** Describes a single dirty property entry, tracking the original value before changes. */
interface SmartModelDirtyEntry {
property: string;
originalValue: any;
}
/** Options accepted by `export` / `__export`. */
interface SmartModelExportOptions {
/** If provided, only the listed property names will be exported. */
fields?: string[];
}
/** Options accepted by `import` / `__import`. */
interface SmartModelImportOptions {
/** If `true`, properties present in `data` but missing on the model will be created. Default: `false`. */
createMissingProperties?: boolean;
/** If `true`, empty/falsy values in `data` are still matched onto the model. Default: `true`. */
matchEmptyParameters?: boolean;
}
/** Options accepted by `importField`. */
interface SmartModelImportFieldOptions {
/** If `true`, `undefined`/`null` values are accepted. Default: `false`. */
acceptUndefined?: boolean;
/** Explicit data type for parsing. `"auto"` performs no conversion. Default: `"auto"`. */
dataType?: "auto" | "boolean" | "date" | "number";
/** Fallback value used when the parsed number is `NaN`. Only for `dataType: "number"`. */
defaultValue?: number;
/** If `true`, date values are parsed as UTC and converted to local time. Default: `true`. */
useUTC?: boolean;
}
/**
* Payload dispatched with `"dataChanged"` and `"<propertyName>Changed"` events.
*/
interface SmartModelChangeEventData {
/** The `__modelName` of the model that triggered the change. */
modelName: string | undefined;
/** The new value assigned to the property. */
newValue: any;
/** The previous value of the property before the change. */
oldValue: any;
/** The dot-notation path of the property that changed. */
property: string;
}
/** Describes an item used in array-form calls to `set()`. */
interface SmartModelSetItem {
name: string;
value: any;
}
/** Inferred type map produced by `SmartModel.inferTypes()`. */
type SmartModelInferredTypes = Record<
string,
"number" | "datetime" | "boolean" | "string" | "array" | "object" | typeof SmartModel
>;
/**
* Base class for reactive data models in the Shark MVC framework.
*
* @fires SmartModel#dataChanged - Emitted whenever a property changes value.
* @fires SmartModel#<propertyName>Changed - Emitted for the specific changed property.
*/
export declare class SmartModel extends EventTarget {
/** Unique GUID assigned at construction. Non-writable. */
readonly __shguid: string;
/** Whether any property of this model differs from its original/cleaned value. */
__isDirty: boolean;
/** List of properties that have changed since the last `clean()` call. */
__dirtyProperties: SmartModelDirtyEntry[];
/** Auto-bound property names (reserved for internal use). */
__boundProperties: string[];
/** The model structure passed at construction, if any. */
__modelStructure: object | undefined;
/** The renderer instance associated with this model. */
__renderer: any;
// ── Private methods exposed via Object.defineProperty ────────────────────
protected readonly __clean: (properties?: string[]) => SmartModelDirtyEntry[];
protected readonly __export: (options?: SmartModelExportOptions) => Record<string, any>;
protected readonly __import: (data: object, options?: SmartModelImportOptions) => any;
protected readonly __restore: (properties?: string[]) => void;
protected readonly __setPropertyDirtiness: (propertyName: string, eventData: SmartModelChangeEventData) => void;
protected readonly __toJSON: (forcePrivateProperties?: boolean) => Record<string, any>;
/**
* Creates a new SmartModel instance.
*/
constructor(data?: object, options?: SmartModelImportOptions, modelStructure?: object);
/** Replace the renderer instance for this model. */
addRenderer(renderer: { setModel(model: SmartModel): void; [key: string]: any }): void;
/**
* Clear dirty state for all properties, or only the specified ones.
* After this call, `__isDirty` is recomputed.
*/
clean(properties?: string[]): SmartModelDirtyEntry[];
/** Dispatch an event from this model using jQuery's `.trigger()`. */
emit(eventName: string, eventData?: any): JQuery;
/**
* Export all public, non-function properties to a plain object.
* Properties starting with `__` or `jQuery` are excluded by default.
*/
export(options?: SmartModelExportOptions): Record<string, any>;
/** Export an array field as a JSON string. */
exportArrayField(field: string, format?: "JSON"): string;
protected __exportArrayField(field: string, format?: "JSON"): string;
/** Export a boolean field as a number (`1`/`0`) or string. */
exportBooleanField(field: string, format?: "number" | "string"): number | string;
protected __exportBooleanField(field: string, format?: "number" | "string"): number | string;
/** Export a date field as a formatted string. */
exportDateField(field: string, format?: "ISO", useUTC?: boolean): string;
protected __exportDateField(field: string, format?: "ISO", useUTC?: boolean): string;
/** Export a numeric field, parsing strings to floats if necessary. */
exportNumberField(field: string, format?: string): number;
protected __exportNumberField(field: string, format?: string): number;
/** Export an object field as a JSON string. */
exportObjectField(field: string, format?: "JSON"): string;
protected __exportObjectField(field: string, format?: "JSON"): string;
/** Import data from a plain object onto this model. */
import(data: object, options?: SmartModelImportOptions): any;
/** Import and parse a boolean field from a raw value. */
importBooleanField(field: string, value: any): boolean;
protected __importBooleanField(field: string, value: any): boolean;
/** Import and parse a date field, converting it to a `moment` object. */
importDateField(field: string, value: any, useUTC?: boolean): boolean;
protected __importDateField(field: string, value: any, useUTC?: boolean): boolean;
/** Import and parse a numeric field. */
importNumberField(field: string, value: any, defaultValue?: number): boolean;
protected __importNumberField(field: string, value: any, defaultValue?: number): boolean;
/**
* Unified import helper that dispatches to the correct typed import method
* based on `options.dataType`.
*/
importField(field: string, value: any, options?: SmartModelImportFieldOptions): boolean;
/**
* Inspect an instance of this model class and infer the data type of each public property.
* Results are also logged to the console.
*/
static inferTypes(): void;
/**
* Check whether this model has unsaved/unclean property changes.
*
* @param properties - A single property path or array of paths to check.
* @param mode - `"any"` (default): `true` if at least one listed property is dirty.
* `"all"`: `true` only if every listed property is dirty.
*/
isDirty(properties?: string | string[] | null, mode?: "any" | "all"): boolean;
/** Remove an event listener from this model using jQuery's `.off()`. */
off(eventName: string, eventHandler: (...args: any[]) => any): JQuery;
/** Attach an event listener to this model using jQuery's `.on()`. */
on(eventName: string, eventHandler: (...args: any[]) => any): JQuery;
/**
* Register a property as bound to a View (called internally by `parseTemplates`).
*
* @param propertyName - The model property name.
* @param viewClassName - The `className` of the View that references this property.
*/
registerBoundProperty(propertyName: string, viewClassName: string): void;
/** Restore dirty properties to their original values and reset `__isDirty` to `false`. */
restore(properties?: string[]): void;
protected __singleSet(name: string, value: any, eventTargetNode?: Element | null): void;
/**
* Assign a value to one or more model properties.
*
* @fires SmartModel#dataChanged
* @fires SmartModel#<propertyName>Changed
*/
set(name: string, value: any, eventTargetNode?: Element | null): void;
set(name: Record<string, any>, eventTargetNode?: Element | null): void;
set(name: SmartModelSetItem[], eventTargetNode?: Element | null): void;
/** Serialize this model to a plain object. */
toJSON(forcePrivateProperties?: boolean): Record<string, any>;
/** Serialize this model to a JSON string. */
toString(forcePrivateProperties?: boolean): string;
/** @experimental Sync a single property value to all matching `[data-sh-prop]` DOM elements. */
updateScreenWithModelValue(
name: string,
eventTargetNode: Element | null,
value: any,
eventData: SmartModelChangeEventData | string
): void;
/** @experimental @dangerous Sync all public properties to their matching `[data-sh-prop]` DOM elements. */
updateScreenWithModelValues(): void;
// ── Optional lifecycle hooks ──────────────────────────────────────────────
/** Called whenever any property changes. Override in subclasses. */
onDataChanged?(eventData: SmartModelChangeEventData): void;
/** Catch-all index signature for dynamic `on<PropertyName>Changed` hooks. */
[key: string]: any;
}
// ── SwimRoute ─────────────────────────────────────────────────────────────────
/** Data object accepted by the `SwimRoute` constructor. */
interface SwimRouteData {
/** Route pattern, optionally containing dynamic segments prefixed with `:` (e.g. `"/user/:id"`). */
route: string;
/** The View instance or class associated with this route. */
view: any;
}
/**
* Represents a single application route, pairing a URL pattern with a View
* and providing dynamic parameter extraction.
*/
export declare class SwimRoute {
/** The route pattern string (e.g. `"/user/:id/orders/:orderId"`). */
route: string;
/** The View instance or class associated with this route. */
view: any;
/** The last matched URL for this route. */
url: string;
/** The dynamic parameters extracted from the last successful `extractRouteParams()` call. */
readonly params: Record<string, string>;
constructor(data: SwimRouteData);
/**
* Match a URL against this route's pattern and extract any dynamic segment values.
*
* @param url - The URL to match. Falls back to `this.url` if omitted.
*/
extractRouteParams(url?: string): void;
}
// ── SwimWay ───────────────────────────────────────────────────────────────────
/**
* The basic router for the Shark environment.
* Fun Fact: "SwimWay" is the name given by researchers to a route followed in the ocean by sharks.
*/
export declare class SwimWay {
/**
* @param routingMethod - One of `"pathname"` (default), `"url"`, or `"hash"`.
*/
constructor(routingMethod?: "pathname" | "url" | "hash");
/** Add a single route. Returns the new total number of routes. */
addRoute(route: SwimRouteData | SwimRoute): number;
/** Add multiple routes at once. Returns the new total number of routes. */
addRoutes(routes: (SwimRouteData | SwimRoute)[]): number;
/** Navigate to a route by path string or SwimRoute object. */
navigateTo(route: string | SwimRoute): boolean;
/**
* Parse `document.location` and return the View name encoded in the current URL.
*/
getRequestedViewFromLocation(): string;
/**
* Update URLSearchParams in the current URL without creating a new browser history entry.
*
* @param urlSearchParams - A string, plain object, or `URLSearchParams` instance.
*/
setURLSearchParams(urlSearchParams: string | object | URLSearchParams): void;
}
// ── Utils ─────────────────────────────────────────────────────────────────────
export declare class Utils {
/**
* Return a random number between 0 and `max`, or between `minOrMax` and `max`.
*/
static betterRandom(minOrMax: number, max?: number): number;
/**
* Return a random integer between 0 and `max`, or between `minOrMax` and `max`.
*/
static betterRandomInt(minOrMax: number, max?: number): number;
/** Convert a camelCase string to dash-separated lowercase words. */
static camelToDashed(inputString: string): string;
/** Compute a simple numeric hash of any serializable value. */
static computeHash(value: any): number;
/** Convert a dash-separated string to camelCase. */
static dashedToCamel(inputString: string): string;
/** Decode common HTML entities in a string. */
static decodeHtmlEntities(str: string): string;
/** Deep-merge any number of objects into a new object. */
static deepMerge(...objects: object[]): object;
/** Return an object containing only the properties that differ between two objects. */
static extractDifferentProperties(firstObject: object, secondObject: object): object;
/**
* Parse the attributes of an HTML opening tag string into a plain object.
*
* - `data-*` attributes are grouped under `result.dataset` (camelCase keys, no `data-` prefix).
* - `prop-*` attributes are grouped under `result.props` (camelCase keys, no `prop-` prefix).
* These are **not** passed to the HTML tag root; they are used exclusively for `{{props.xxx}}`
* interpolation in component templates.
* - All other attributes are returned as camelCase top-level keys.
*/
static extractPropertiesFromHTMLString(html: string): {
dataset?: Record<string, any>;
props?: Record<string, any>;
[key: string]: any;
};
/**
* Extract all top-level `<shark-xxx>` component tags from a template string.
*
* Unlike a simple regex, this function correctly handles nested shark components
* by counting open/close depth, so slot content containing other `<shark-yyy>` tags
* is never truncated.
*
* @param template - The HTML template string to scan.
* @returns Array of full tag strings for every top-level shark component found.
*/
static extractSharkComponentTags(template: string): string[];
/** Flatten a nested object into a single-level object (non-recursive values only). */
static flatten(objectToFlatten: object): object;
/** Generate a random GUID string, optionally with a prefix and/or suffix. */
static generateGUID(prefix?: string, suffix?: string): string;
/**
* Get a property value from an object by dot-notation path.
* Returns `undefined` if the path does not exist.
*/
static getDeepProperty(object: object, path: string): any;
/**
* Cross-reference `sourceList` items against named lookup lists, attaching matched
* objects as `__<propertyName>` properties (looks for `*Guid` properties as foreign keys).
*/
static matchLists(sourceList: any[], matchLists: Record<string, any[]>, options?: object): void;
/**
* Copy properties from `sourceObject` onto `targetObject`, with configurable rules
* for missing properties, empty values, and undefined values.
*/
static matchProperties(
targetObject: object,
sourceObject: object,
options?: {
createMissingProperties?: boolean;
matchEmptyParameters?: boolean;
matchUndefinedParameters?: boolean;
exclude?: string[];
}
): void;
/**
* Merge the attributes of `includeTag` onto the root element of `templateHtml`.
*
* - `class` values are concatenated.
* - `style` values are merged (includeTag wins on conflicts).
* - `data-*` attributes are merged (includeTag wins on conflicts).
* - `prop-*` attributes are **excluded** — they are never serialized onto the HTML tag.
* - All other attributes are overwritten by `includeTag`.
*/
static mergeAttributes(templateHtml: string, includeTag: string): string;
/** Merge two inline style strings, with `includeStyle` winning on conflicts. */
static mergeStyle(templateStyle: string, includeStyle: string): string;
/** Move the caret to the end of an input or textarea element. */
static moveCaretToEnd(target: HTMLInputElement | HTMLTextAreaElement): void;
/** Flatten a (possibly nested) object into an array of dot-notation property paths. */
static objectToFlatMap(object: object, parent?: string): string[];
/** Safely parse a JSON string, returning a typed fallback on failure. */
static parseJSON(data: string, expectedType?: "array" | "object" | string): any;
/** Add `_index` and `_displayIndex` properties to every item in an array (mutates in place). */
static reindexList(list: any[]): void;
/**
* Replace `null` or `undefined` values in an object or array with `replaceVal` (mutates in place).
*/
static replaceNullValue(objectToParse: object | any[], replaceVal: any): void;
/**
* Resolve named slot content and `prop-*` interpolation for a component template.
*
* Given the component's own template HTML and the full usage tag from the parent
* template, this function:
* 1. Extracts `<slot name="...">content</slot>` children from the usage tag and
* substitutes them into matching `<slot name="..."></slot>` placeholders in the
* component template.
* 2. Extracts `prop-*` attributes from the usage tag and substitutes them into
* `{{props.xxx}}` placeholders in the component template.
*
* Unmatched slots and unmatched `{{props.xxx}}` placeholders are left unchanged
* as implicit fallback content.
*
* @param componentTemplate - The component's own HTML template string.
* @param componentUsageTag - The full usage tag as written in the parent template,
* e.g. `<shark-foo prop-id="bar"><slot name="title">Hi</slot></shark-foo>`.
* @returns The resolved template string.
*/
static resolveComponentSlots(componentTemplate: string, componentUsageTag: string): string;
/** JSON-stringify a value, safely handling circular references and `Map` instances. */
static safeStringify(sourceValue: any): string;
/**
* Shorthand for `searchArrayForProperty`.
*/
static safp(
list: any[],
property: string,
value: any,
searchType?: boolean | "first" | "all" | "index"
): object | any[] | number | null;
/**
* Search an array for the first object (or all objects) whose named property equals `value`.
*
* @param searchType - `"first"` (default): return the first match.
* `"all"`: return an array of all matches.
* `"index"`: return the index of the first match.
* Passing `true` is equivalent to `"first"`; `false` to `"all"`.
*/
static searchArrayForProperty(
list: any[],
property: string,
value: any,
searchType?: boolean | "first" | "all" | "index"
): object | any[] | number | null;
/**
* Assign a value to a property on an object, creating intermediate objects as needed.
*/
static setDeepProperty(object: object, path: string, value: any, depth?: number): object;
/** Legacy trace utility. Prefer `Shark.trace()`. */
static trace(content: string): void;
/** Lowercase the first character of a string. */
static toCamelCase(value: string): string;
/** Uppercase the first character of a string, optionally lowercasing the rest. */
static toTitleCase(value: string, forceLowerCase?: boolean): string;
/** Pad a number with leading zeros to a given total length. */
static zeroFill(number: number, length: number): string;
}