/** * Type declarations for the slotting components (dist/widget/slotting-components.js). * Hand-maintained; widget/dts-check.ts fails `npm run typecheck` when the implementation drifts from it. * * Loading the module defines four custom elements (plain web components, no framework runtime): * * draws one wall of a layout with its bins (calls the API only with `lights`) * the operator's scan screen: scan -> the bin to put the item in * the bin setup editor: import (DXF, CSV, grid, photo), preview, edit, save * progress of a wave, polling the API * * Common attributes: `theme` (`light` default, `dark`, `auto`). The API components also take `api-base`, the base URL of the * slotting install (default: the origin the script was loaded from). They check `GET ${api-base}/v1/info` (cached 10 min) * and fail open when it can't be reached; when the install is not licensed they show a neutral "not licensed, contact your * administrator" notice (part `blocked`) instead of their content, and on a trial license a TRIAL mark (part `trial-mark`, * fixed colours, not themable). * * Theming: CSS custom properties `--sl-*` (inherited through the shadow root; see TOKENS for the list with light and dark * defaults) and the `part`s listed on each class (style them with ::part()). * * Events are CustomEvents that bubble and are composed (they cross shadow boundaries). * * Frameworks: * - React 19: custom elements work as they are (properties and events). The JSX helper below types the tags: * declare module 'react' { namespace JSX { interface IntrinsicElements extends SlottingIntrinsicElements {} } } * - Vue 3: tell the compiler they are custom elements (`compilerOptions.isCustomElement: tag => tag.startsWith('slotting-')`) * and, for template type checking, declare them as GlobalComponents: * declare module 'vue' { interface GlobalComponents { 'slotting-station': DefineComponent } } * (DefineComponent from 'vue'; this file doesn't import vue, so it can't declare that for you.) * - Angular: add CUSTOM_ELEMENTS_SCHEMA to the component or module that uses them. Svelte and plain HTML: nothing to do. */ // ---------- domain and wire types (the HTTP API's JSON) ---------- export type LengthUnit = 'mm' | 'cm' | 'm' | 'in' | 'ft'; export type WeightUnit = 'kg' | 'g' | 'lb' | 'oz'; export interface Layout { id: string; name?: string; unit: LengthUnit; weightUnit: WeightUnit; fillFactor?: number; walls: Wall[]; } export interface Wall { id: string; name?: string; kind: 'fixed' | 'mobile'; position?: { x: number; y: number }; rotation?: number; bins: Bin[]; } export interface Bin { id: string; x: number; y: number; width: number; height: number; depth: number; maxWeight?: number; maxUnits?: number; disabled?: boolean; /** Address of the bin's put-to-light module (1..64 of A-Z a-z 0-9 . _ : / -, unique per layout). Default: the bin id. */ lightAddress?: string; } export interface BinRef { id: string; wallId: string; } export interface Put { binId: string; wallId: string; sku: string; quantity: number; } export type OrderStatus = 'queued' | 'planned' | 'in_progress' | 'complete' | 'released'; export interface OrderState { id: string; status: OrderStatus; bins: BinRef[]; unitsTotal: number; unitsPut: number; remaining: Record; short?: boolean; unplaceable?: Record; } export interface BinState { id: string; wallId: string; waveId?: string; orderId?: string; units: number; fill: number; weight: number; } export interface WaveStats { orders: number; ordersComplete: number; ordersQueued: number; unitsTotal: number; unitsPut: number; binsUsed: number; } export type WaveStatus = 'active' | 'complete' | 'closed'; export interface WaveState { id: string; layoutId: string; status: WaveStatus; createdAt: string; orders: OrderState[]; stats: WaveStats; } export interface ScanResult { scanId: string; waveId: string; sku: string; quantity: number; orderId: string; bin: BinRef; puts: Put[]; order: OrderState; orderComplete: boolean; orderBins?: BinRef[]; waveComplete: boolean; /** True when the wave confirms puts (`options.confirmPuts`) and this put awaits its confirmation (`orderComplete` is then false). */ pending?: boolean; } /** `POST /api/waves/:waveId/confirm` answer (also the `confirm` event's detail). */ export interface ConfirmResult { scanId: string; waveId: string; orderId: string; bin: BinRef; order: OrderState; orderComplete: boolean; orderBins?: BinRef[]; waveComplete: boolean; } /** `GET /api/layouts/:layoutId/lights`: the desired state of one bin's light (bins not listed are off). */ export interface LightState { binId: string; wallId: string; /** Bin.lightAddress, else the bin id. */ address: string; /** `put`: put `quantity` units here. `complete`: the order in this bin is complete (pack out). */ state: 'put' | 'complete'; waveId: string; orderId: string; quantity?: number; sku?: string; } /** .pendingConfirmation: a put waiting for its bin scans. `bins`: the bins still to confirm. */ export interface PendingConfirmation { scanId: string; waveId: string; orderId: string; sku: string; quantity: number; bins: Array; /** The scan being confirmed. */ result: ScanResult; } // ---------- event details ---------- /** `bin-select` (): a bin was clicked. `state` is its occupancy when `bins` has it. */ export interface BinSelectDetail { binId: string; wallId: string; bin: Bin; state: BinState | null; } /** `scan` (): a code was scanned; the request is about to be sent. */ export interface ScanEventDetail { sku: string; quantity: number; scanId: string; } /** * `light-press` (): a lit bin was clicked and POST /api/lights/press answered. `result` is the * answer on success, `error` the failure (then `result` is null). */ export interface LightPressDetail { layoutId: string; binId: string; wallId: string; address: string; state: LightState['state']; result: Record | null; error: { status: number; code: string; message: string } | null; } /** * `order-complete` (): the order is complete. `bins`: all its bins, nearest first. `result`: the scan that * completed it or, with confirmPuts, the scan whose confirmation completed it; `confirm`: that confirmation (absent when the * put was confirmed outside the station, e.g. by the bin's light button). */ export interface OrderCompleteDetail { orderId: string; bins: BinRef[]; result: ScanResult; confirm?: ConfirmResult; } /** * How the station shows a failed scan. `aside`: put the unit aside (not in the wave, fully slotted, no bin free, too large). * `retry`: offline, no answer or busy (the operator can resend the same scan). `blocked`: not licensed. `error`: anything else. */ export type ScanErrorKind = 'aside' | 'retry' | 'blocked' | 'error'; /** * `scan-error` (). `code`: the API's code (`sku_not_in_wave`, `sku_fully_slotted`, `no_bin_available`, * `item_too_large`, `wave_closed`, `unknown_wave`, `wrong_bin`, `not_pending`, `license_required`, ...) or a client code: * `offline`, `timeout`, `client_error`, `confirm_first` (an item was scanned while a put awaits its bin scan; it was not * sent). `status` is 0 when no HTTP answer was received. While a put is pending, `sku` is the scanned code (a bin id). */ export interface ScanErrorDetail { sku: string; quantity: number; scanId: string; status: number; code: string; message: string; kind: ScanErrorKind; } export type ImportSource = 'dxf' | 'csv' | 'grid' | 'photo'; /** `layout-imported` (): an import returned a draft, now merged into the editor's layout. */ export interface LayoutImportedDetail { source: ImportSource; layout: Layout; warnings: string[]; } /** `layout-saved` (). `created`: POST (new layout); otherwise PUT replaced it. */ export interface LayoutSavedDetail { layout: Layout; created: boolean; } /** A problem with the editor's draft. `binId` / `field` point at the table cell when known. */ export interface DraftIssue { message: string; wallId?: string; binId?: string; field?: string; } export interface SlottingEventMap extends HTMLElementEventMap { 'bin-select': CustomEvent; 'scan': CustomEvent; 'put': CustomEvent; 'confirm': CustomEvent; 'light-press': CustomEvent; 'order-complete': CustomEvent; 'scan-error': CustomEvent; 'layout-imported': CustomEvent; 'layout-saved': CustomEvent; 'wave-update': CustomEvent; } // ---------- theming ---------- export type Theme = 'light' | 'dark' | 'auto'; /** The public tokens: [name without `--sl-`, light default, dark default, what it is for]. */ export declare const TOKENS: readonly (readonly [name: string, light: string, dark: string, description: string])[]; /** Forgets the cached /v1/info answers (e.g. after installing a license key), so the next component asks again. */ export declare function clearInstallInfo(): void; // ---------- elements ---------- /** * Shared by the four components. Parts on every component: `content` (the wrapper of the content), `blocked` (the "not * licensed" notice), `trial-mark`. */ export declare abstract class SlottingElement extends HTMLElement { static readonly observedAttributes: string[]; /** The `theme` attribute. */ get theme(): Theme; set theme(value: Theme); /** The `api-base` attribute; default: the origin the components were loaded from. */ get apiBase(): string; set apiBase(value: string); /** True while the "not licensed" notice is shown instead of the content. */ get blocked(): boolean; attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void; addEventListener(type: K, listener: (this: this, ev: SlottingEventMap[K]) => unknown, options?: boolean | AddEventListenerOptions): void; addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; removeEventListener(type: K, listener: (this: this, ev: SlottingEventMap[K]) => unknown, options?: boolean | EventListenerOptions): void; removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; } /** * ``: one wall face as SVG, scaled to the element's width (height capped by `--sl-wall-height`). Bins show * their id and state: free, occupied, disabled, the `order-id` order's bins, `highlight`ed bins (pulsing, steady with * prefers-reduced-motion) and the `selected` bin. Attributes: `wall-id`, `order-id`, `theme`; for put-to-light `lights`, * `layout-id` (default: the layout's id), `api-base`, `lights-interval`, `simulate-buttons`. Events: `bin-select`, * `light-press`. * * Without `lights` it never calls the API. With `lights` it polls GET {api-base}/api/layouts/{layout-id}/lights every * `lights-interval` ms (default 1000, at least 500; paused while the page is hidden, stopped when removed, backing off on * failures) and shows lit bins: `put` pulsing in --sl-light-put with the quantity, `complete` steady in --sl-light-complete * (steady and thicker outline with prefers-reduced-motion). With `simulate-buttons` a click (or Enter / Space) on a lit bin * presses its button: POST /api/lights/press { layoutId, address }, then `light-press`. * Parts: wall, caption, svg, bin (the highlighted bins also `bin-target`; lit bins `bin-lit` and `bin-lit-put` / * `bin-lit-complete`), lights-status, empty. */ export declare class SlottingWall extends SlottingElement { get layout(): Layout | null; set layout(value: Layout | null); /** `wall-id`; default: the layout's first wall. */ get wallId(): string | null; set wallId(value: string | null); /** Occupancy (GET /api/layouts/:layoutId/bins). Bins without an entry are free. */ get bins(): BinState[]; set bins(value: BinState[] | null); /** Bin ids to highlight. */ get highlight(): string[]; set highlight(value: string[] | null); /** `order-id`: the order whose bins are emphasised. */ get orderId(): string | null; set orderId(value: string | null); /** The selected bin (outlined). */ get selected(): string | null; set selected(value: string | null); /** `lights`: poll and show the layout's light states. */ get lights(): boolean; set lights(value: boolean); /** `lights-interval` in ms (default 1000, at least 500). */ get lightsInterval(): number; set lightsInterval(value: number); /** `simulate-buttons`: a click on a lit bin presses its light button. */ get simulateButtons(): boolean; set simulateButtons(value: boolean); /** `layout-id`: the layout whose lights are polled; default: the id of `layout`. */ get layoutId(): string | null; set layoutId(value: string | null); /** The light states of the last poll (every wall of the layout). */ get lightStates(): LightState[]; /** The wall being drawn. */ get wall(): Wall | null; connectedCallback(): void; disconnectedCallback(): void; } /** * ``: the operator's scan screen. Attributes: `api-base`, `wave-id`, `layout-id` (optional), `operator` (sent * with each scan), `no-keyboard` (no on-screen keyboard on handhelds), `theme`. The scan field keeps the focus and takes * keyboard-wedge input (keystrokes + Enter or Tab; several codes separated by line breaks also work). Scans run one at a * time in order; each has a client scanId, so Retry after "offline" can't put a unit twice. * Events: `scan` (every scanned code), `put` (ScanResult), `confirm` (ConfirmResult), `order-complete`, `scan-error`. * * Waves with `confirmPuts`: a scan answering `pending: true` shows "Scan bin to confirm" (target part `target-pending`, * --sl-target-pending). The next scanned bin id is sent to POST /api/waves/:waveId/confirm (one confirmation per bin of a * split put); a wrong bin shows "Wrong bin" with the expected bin and keeps waiting; an item scanned meanwhile is refused * without calling the API (`scan-error` code `confirm_first`): confirm the put or press Undo. `put` fires on the scan, * `confirm` on each confirmation, `order-complete` on the confirmation that completes the order. While a put is pending the * station polls GET /api/layouts/:layoutId/lights (every 1.5 s, page visible) to notice a confirmation by light button. * Without confirmPuts nothing of this applies. * * Parts: station, header, wave, wave-status, wave-progress, wave-progress-text, offline, scan-row, input, scan-input, * quantity-input, button, button-primary, scan-button, undo-button, retry-button, dismiss-button, target (also target-idle, * target-pending, target-confirmed), target-caption, target-bin, target-detail, confirm-prompt, alert (also alert-aside, * alert-retry, alert-error), info, order, order-id, order-units, order-progress, remaining, order-complete, order-bins, * bin-chip, wall (the inner ). */ export declare class SlottingStation extends SlottingElement { get waveId(): string | null; set waveId(value: string | null); get layoutId(): string | null; set layoutId(value: string | null); get operator(): string | null; set operator(value: string | null); /** The wave as loaded, updated by each scan. */ get wave(): WaveState | null; /** The last successful scan's result. */ get lastResult(): ScanResult | null; /** The put waiting for its bin confirmation (confirmPuts), with the bins still to scan; null otherwise. */ get pendingConfirmation(): PendingConfirmation | null; /** Scans a code as if the scanner sent it. Resolves when done (outcome via events); TypeError for an empty code or bad quantity. */ scan(sku: string, quantity?: number): Promise; /** Undoes the last successful scan. */ undo(): Promise; /** Focuses the scan field. */ focus(options?: FocusOptions): void; connectedCallback(): void; disconnectedCallback(): void; } /** * ``: the bin setup editor. Attributes: `api-base`, `layout-id` (edit that stored layout: GET to load, PUT to * save; without it Save POSTs a new layout), `theme`. Photo import is disabled with an explanation when /v1/info says no AI * provider is configured. Events: `layout-imported`, `layout-saved`. * Parts: setup, header-fields, field, hint, input, file-input, import, tabs, tab, import-dxf, import-csv, import-grid, * import-photo, ai-note, import-button, import-status, warnings, preview, wall-picker, wall-button, wall-fields, * delete-wall, wall (the inner ), bins, table, row (also row-invalid, row-selected), delete-bin, pager, * issues, validate-button, save-button, save-status, button, button-primary. */ export declare class SlottingSetup extends SlottingElement { /** The draft (a copy). Setting it replaces the draft; null starts an empty one. */ get layout(): Layout; set layout(value: Layout | null); get layoutId(): string | null; set layoutId(value: string | null); /** Checks the draft (ids, dimensions, overlaps, limits) and shows the problems. */ validate(): DraftIssue[]; /** Validates and saves (POST, or PUT when editing). Rejects when invalid or refused; the problems are shown inline. */ save(): Promise; connectedCallback(): void; disconnectedCallback(): void; } /** * ``: a wave's stats, its walls with the occupied bins, and its orders. Polls every `interval` ms * (default 3000, minimum 1000) while the page is visible, until the wave is closed. Attributes: `api-base`, `wave-id`, * `layout-id` (only when two layouts have the same wave id), `interval`, `theme`. Event: `wave-update`. * Parts: monitor, header, wave, wave-status, updated, error, tiles, tile, value, label, wave-progress, walls, wall, * status-filter, input, table, row. */ export declare class SlottingWaveMonitor extends SlottingElement { get waveId(): string | null; set waveId(value: string | null); get layoutId(): string | null; set layoutId(value: string | null); get interval(): number; set interval(value: number); /** The wave as last polled. */ get wave(): WaveState | null; /** Polls now. */ refresh(): Promise; connectedCallback(): void; disconnectedCallback(): void; } declare global { interface HTMLElementTagNameMap { 'slotting-wall': SlottingWall; 'slotting-station': SlottingStation; 'slotting-setup': SlottingSetup; 'slotting-wave-monitor': SlottingWaveMonitor; } } // ---------- JSX helpers (React and other JSX frameworks) ---------- /** * Attributes common to the components in JSX. Listen to the events with a ref and addEventListener (the event names have * dashes). Object properties (`layout`, `bins`, ...) are set by React 19 as properties; older versions need a ref. */ export interface SlottingBaseProps { theme?: Theme; 'api-base'?: string; id?: string; class?: string; className?: string; hidden?: boolean; slot?: string; ref?: unknown; key?: string | number; } export interface SlottingWallProps extends SlottingBaseProps { 'wall-id'?: string; 'order-id'?: string; 'layout-id'?: string; lights?: boolean; 'lights-interval'?: number | string; 'simulate-buttons'?: boolean; /** React 19 sets unknown props on custom elements as properties. */ layout?: Layout | null; bins?: BinState[] | null; highlight?: string[] | null; selected?: string | null; } export interface SlottingStationProps extends SlottingBaseProps { 'wave-id'?: string; 'layout-id'?: string; operator?: string; 'no-keyboard'?: boolean; } export interface SlottingSetupProps extends SlottingBaseProps { 'layout-id'?: string; } export interface SlottingWaveMonitorProps extends SlottingBaseProps { 'wave-id'?: string; 'layout-id'?: string; interval?: number | string; } /** Merge into your framework's JSX.IntrinsicElements (see the header). */ export interface SlottingIntrinsicElements { 'slotting-wall': SlottingWallProps; 'slotting-station': SlottingStationProps; 'slotting-setup': SlottingSetupProps; 'slotting-wave-monitor': SlottingWaveMonitorProps; }