@obinexusltd/obix-component-progress 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +44 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/01-overview.md +26 -0
- package/docs/02-usage.md +50 -0
- package/docs/03-accessibility.md +17 -0
- package/docs/04-state-and-html.md +22 -0
- package/package.json +58 -0
- package/src/index.ts +90 -0
- package/src/types.ts +36 -0
- package/test/progress.test.mjs +27 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OBINexus Computing — Nnamdi Michael Okpala
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# @obinexusltd/obix-component-progress
|
|
2
|
+
|
|
3
|
+
**The `ObixProgress` feedback component** — a `role="progressbar"` with a determinate value / min / max or an `indeterminate` busy state, plus optional `aria-valuetext`.
|
|
4
|
+
|
|
5
|
+
Split out of `@obinexusltd/obix-component-feedback` as an independent package.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @obinexusltd/obix-component-progress
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
> **Zero dependencies.** Data-Oriented: `createX(config)` returns
|
|
12
|
+
> `{ name, state, actions, render }`. Actions are pure
|
|
13
|
+
> `(state, …args) => newState`; `render(state)` is deterministic, HTML-escaped
|
|
14
|
+
> markup (an empty string while hidden). `renderX(config, overrides?)` renders
|
|
15
|
+
> in one call.
|
|
16
|
+
|
|
17
|
+
## API
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { createProgress, renderProgress } from "@obinexusltd/obix-component-progress";
|
|
21
|
+
|
|
22
|
+
const el = createProgress({ /* see docs/02-usage.md */ });
|
|
23
|
+
el.render(el.state);
|
|
24
|
+
renderProgress({ /* config */ }); // one call
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Actions** — `setValue(state, n)` · `setIndeterminate(state, bool)` · `complete(state)` · `reset(state)` · `setValueText(state, text)`
|
|
28
|
+
|
|
29
|
+
## Documentation
|
|
30
|
+
|
|
31
|
+
| # | Guide |
|
|
32
|
+
|---|-------|
|
|
33
|
+
| 01 | [Overview](docs/01-overview.md) |
|
|
34
|
+
| 02 | [Usage & API](docs/02-usage.md) |
|
|
35
|
+
| 03 | [Accessibility](docs/03-accessibility.md) |
|
|
36
|
+
| 04 | [State & rendered HTML](docs/04-state-and-html.md) |
|
|
37
|
+
|
|
38
|
+
## Related
|
|
39
|
+
|
|
40
|
+
Part of the OBIX feedback set: `@obinexusltd/obix-component-``toast` · `alert` · `progress` · `loading`.
|
|
41
|
+
|
|
42
|
+
## License
|
|
43
|
+
|
|
44
|
+
MIT — OBINexus Computing
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DOPComponent, ProgressConfig, ProgressState } from "./types.js";
|
|
2
|
+
export type { Action, DOPComponent, ProgressConfig, ProgressState } from "./types.js";
|
|
3
|
+
export declare function createProgress(config: ProgressConfig): DOPComponent<ProgressState>;
|
|
4
|
+
export declare function renderProgress(config: ProgressConfig, overrides?: Partial<ProgressState>): string;
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE9E,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA+BtF,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,YAAY,CAAC,aAAa,CAAC,CAiClF;AAGD,wBAAgB,cAAc,CAC5B,MAAM,EAAE,cAAc,EACtB,SAAS,GAAE,OAAO,CAAC,aAAa,CAAM,GACrC,MAAM,CAGR"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
function esc(value) {
|
|
2
|
+
return String(value ?? "")
|
|
3
|
+
.replace(/&/g, "&")
|
|
4
|
+
.replace(/</g, "<")
|
|
5
|
+
.replace(/>/g, ">")
|
|
6
|
+
.replace(/"/g, """);
|
|
7
|
+
}
|
|
8
|
+
function clamp(n, lo, hi) {
|
|
9
|
+
return Math.min(hi, Math.max(lo, n));
|
|
10
|
+
}
|
|
11
|
+
function renderProgressHtml(state) {
|
|
12
|
+
const span = state.max - state.min || 1;
|
|
13
|
+
const pct = state.indeterminate ? 0 : (clamp(state.value, state.min, state.max) - state.min) / span * 100;
|
|
14
|
+
return (`<div class="obix-progress${state.indeterminate ? " obix-progress--indeterminate" : ""}"` +
|
|
15
|
+
` role="progressbar"` +
|
|
16
|
+
`${state.indeterminate ? "" : ` aria-valuenow="${state.value}"`}` +
|
|
17
|
+
` aria-valuemin="${state.min}" aria-valuemax="${state.max}"` +
|
|
18
|
+
` aria-label="${esc(state.label)}"` +
|
|
19
|
+
`${state.valueText ? ` aria-valuetext="${esc(state.valueText)}"` : ""}` +
|
|
20
|
+
`${state.indeterminate ? ' aria-busy="true"' : ""}>` +
|
|
21
|
+
`<div class="obix-progress__fill"${state.indeterminate ? "" : ` style="width:${pct}%;"`}></div>` +
|
|
22
|
+
`</div>`);
|
|
23
|
+
}
|
|
24
|
+
export function createProgress(config) {
|
|
25
|
+
if (!config || typeof config.label !== "string") {
|
|
26
|
+
throw new TypeError("[obix-component-progress] createProgress: `label` is required");
|
|
27
|
+
}
|
|
28
|
+
const state = {
|
|
29
|
+
value: config.value ?? 0,
|
|
30
|
+
min: config.min ?? 0,
|
|
31
|
+
max: config.max ?? 100,
|
|
32
|
+
indeterminate: config.indeterminate ?? false,
|
|
33
|
+
label: config.label,
|
|
34
|
+
valueText: config.valueText ?? null,
|
|
35
|
+
};
|
|
36
|
+
const actions = {
|
|
37
|
+
setValue: (s, value) => ({
|
|
38
|
+
...s,
|
|
39
|
+
value: Number(value),
|
|
40
|
+
indeterminate: false,
|
|
41
|
+
}),
|
|
42
|
+
setIndeterminate: (s, indeterminate) => ({
|
|
43
|
+
...s,
|
|
44
|
+
indeterminate: Boolean(indeterminate),
|
|
45
|
+
}),
|
|
46
|
+
complete: (s) => ({ ...s, value: s.max, indeterminate: false }),
|
|
47
|
+
reset: (s) => ({ ...s, value: s.min }),
|
|
48
|
+
setValueText: (s, text) => ({
|
|
49
|
+
...s,
|
|
50
|
+
valueText: String(text),
|
|
51
|
+
}),
|
|
52
|
+
};
|
|
53
|
+
return { name: "ObixProgress", state, actions, render: renderProgressHtml };
|
|
54
|
+
}
|
|
55
|
+
export function renderProgress(config, overrides = {}) {
|
|
56
|
+
const progress = createProgress(config);
|
|
57
|
+
return progress.render({ ...progress.state, ...overrides });
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type Action<S> = (state: S, ...args: any[]) => S;
|
|
2
|
+
export interface DOPComponent<S> {
|
|
3
|
+
name: string;
|
|
4
|
+
state: S;
|
|
5
|
+
actions: Record<string, Action<S>>;
|
|
6
|
+
render: (state: S) => string;
|
|
7
|
+
}
|
|
8
|
+
export interface ProgressConfig {
|
|
9
|
+
label: string;
|
|
10
|
+
value?: number;
|
|
11
|
+
min?: number;
|
|
12
|
+
max?: number;
|
|
13
|
+
indeterminate?: boolean;
|
|
14
|
+
valueText?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ProgressState {
|
|
17
|
+
value: number;
|
|
18
|
+
min: number;
|
|
19
|
+
max: number;
|
|
20
|
+
indeterminate: boolean;
|
|
21
|
+
label: string;
|
|
22
|
+
valueText: string | null;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;AAExD,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,CAAC,CAAC;IACT,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAE7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,OAAO,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# ObixProgress — Overview
|
|
2
|
+
|
|
3
|
+
`@obinexusltd/obix-component-progress` is one feedback component from the OBIX component set, packaged
|
|
4
|
+
on its own. It is **Data-Oriented** and has **zero runtime dependencies**.
|
|
5
|
+
|
|
6
|
+
## The shape
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
const progress = createProgress(config);
|
|
10
|
+
progress.name // "ObixProgress"
|
|
11
|
+
progress.state // the full component state (see docs/04)
|
|
12
|
+
progress.actions // pure (state, …args) => state transitions
|
|
13
|
+
progress.render // (state) => string — deterministic, HTML-escaped
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- **No classes, no lifecycle, no DOM.** Data plus pure functions. You own
|
|
17
|
+
the timers, event wiring, and where the HTML string goes.
|
|
18
|
+
- **Deterministic render.** `render(state)` returns the same markup for the
|
|
19
|
+
same state, and an **empty string** while the component is hidden.
|
|
20
|
+
- **Pure actions.** `actions.foo(state, …)` returns a *new* state.
|
|
21
|
+
|
|
22
|
+
## When to use it
|
|
23
|
+
|
|
24
|
+
Use `@obinexusltd/obix-component-progress` when you want just this one piece of feedback UI without
|
|
25
|
+
pulling the whole feedback bundle.
|
|
26
|
+
|
package/docs/02-usage.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# ObixProgress — Usage & API
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
import { createProgress, renderProgress } from "@obinexusltd/obix-component-progress";
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
## `createProgress(config)`
|
|
8
|
+
|
|
9
|
+
Returns a `DOPComponent`: `{ name, state, actions, render }`. Throws
|
|
10
|
+
`TypeError` when a required field is missing or the wrong type.
|
|
11
|
+
|
|
12
|
+
| Field | Type | Default | Notes |
|
|
13
|
+
|-------|------|---------|-------|
|
|
14
|
+
| `label` | `string` | **required** | `aria-label` for the bar |
|
|
15
|
+
| `value` | `number` | `0` | Current value (clamped to `[min, max]` when rendering the fill) |
|
|
16
|
+
| `min` | `number` | `0` | `aria-valuemin` |
|
|
17
|
+
| `max` | `number` | `100` | `aria-valuemax` |
|
|
18
|
+
| `indeterminate` | `boolean` | `false` | Omits `aria-valuenow`, sets `aria-busy="true"`, adds `--indeterminate` |
|
|
19
|
+
| `valueText` | `string` | `null` | `aria-valuetext`, e.g. `"3 of 10 files"` |
|
|
20
|
+
|
|
21
|
+
The full `Config` / `State` interfaces are in
|
|
22
|
+
[`src/types.ts`](../src/types.ts), which ships in the package.
|
|
23
|
+
|
|
24
|
+
## `renderProgress(config, overrides?)`
|
|
25
|
+
|
|
26
|
+
One-call render — `createProgress(config).render({ ...state, ...overrides })`.
|
|
27
|
+
Pass `{ visible: true }` as an override to render a normally-hidden state for
|
|
28
|
+
snapshots / SSR:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
renderProgress(config, { visible: true });
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Actions
|
|
35
|
+
|
|
36
|
+
`setValue(state, n)` (also clears `indeterminate`) · `setIndeterminate(state, bool)` · `complete(state)` (value → `max`) · `reset(state)` (value → `min`) · `setValueText(state, text)`.
|
|
37
|
+
|
|
38
|
+
Every action is `(state, …args) => newState` and **pure**. Thread state through
|
|
39
|
+
them (directly or via your store) and re-render:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
const p = createProgress({ label: "Sync", max: 50 });
|
|
43
|
+
let s = p.actions.setValue(p.state, 20);
|
|
44
|
+
console.log(p.render(s)); // aria-valuenow="20" style="width:40%;"
|
|
45
|
+
s = p.actions.complete(s);
|
|
46
|
+
console.log(p.render(s)); // aria-valuenow="50" style="width:100%;"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`render(state)` returns `""` whenever the component is not visible — assigning
|
|
50
|
+
it to `innerHTML` cleanly removes the element.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# ObixProgress — Accessibility
|
|
2
|
+
|
|
3
|
+
The rendered HTML carries its ARIA inline — there is no separate a11y layer.
|
|
4
|
+
|
|
5
|
+
- Live-region semantics are built in: `role` / `aria-live` / `aria-atomic`
|
|
6
|
+
are set from state (e.g. assertive for errors, polite for status).
|
|
7
|
+
- All interpolated text (messages, labels) is HTML-escaped.
|
|
8
|
+
- Dismiss controls are real `<button type="button">` with an `aria-label`.
|
|
9
|
+
|
|
10
|
+
## Your responsibilities
|
|
11
|
+
|
|
12
|
+
- Insert the rendered string into a container that already exists in the DOM
|
|
13
|
+
so the live region is announced when content changes.
|
|
14
|
+
- Drive timers yourself (e.g. call `dismiss` after `state.duration` ms).
|
|
15
|
+
- Manage focus: for a dismissible alert, return focus to the triggering
|
|
16
|
+
element (`returnFocusId` is carried in state for you to use).
|
|
17
|
+
- Render one instance per container; give toasts a unique `id`.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# ObixProgress — State & rendered HTML
|
|
2
|
+
|
|
3
|
+
## State
|
|
4
|
+
|
|
5
|
+
The full state object is defined by the `State` interface in
|
|
6
|
+
[`src/types.ts`](../src/types.ts), built from `config` by
|
|
7
|
+
`createProgress`. Plain JSON-serialisable data.
|
|
8
|
+
|
|
9
|
+
## Rendered HTML
|
|
10
|
+
|
|
11
|
+
`render(state)` returns a single element as a string, or **`""`** when the
|
|
12
|
+
component is not visible. Attribute order is fixed; only state-driven
|
|
13
|
+
attributes/branches change between renders.
|
|
14
|
+
|
|
15
|
+
Class names follow the `obix-*` convention
|
|
16
|
+
(`obix-progress`, `obix-progress--<modifier>`).
|
|
17
|
+
No CSS ships with the package.
|
|
18
|
+
|
|
19
|
+
## Testing
|
|
20
|
+
|
|
21
|
+
`test/*.test.mjs` (Node’s built-in runner) asserts the rendered string and
|
|
22
|
+
action purity. Run `npm run build && npm test`.
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@obinexusltd/obix-component-progress",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OBIX ObixProgress — a role=\"progressbar\" with a determinate value / min / max or an indeterminate busy state, plus optional aria-valuetext. Data-Oriented, zero dependencies.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "OBINexus Computing — Nnamdi Michael Okpala <okpalan@protonmail.com>",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"private": false,
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./src": "./src/index.ts",
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"src",
|
|
22
|
+
"dist",
|
|
23
|
+
"docs",
|
|
24
|
+
"test",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"directories": {
|
|
29
|
+
"lib": "dist",
|
|
30
|
+
"doc": "docs",
|
|
31
|
+
"test": "test"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -p tsconfig.json",
|
|
35
|
+
"test": "node --test \"test/*.test.mjs\"",
|
|
36
|
+
"prepublishOnly": "npm run build"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"obix",
|
|
40
|
+
"component",
|
|
41
|
+
"dop",
|
|
42
|
+
"accessibility",
|
|
43
|
+
"progress",
|
|
44
|
+
"progressbar",
|
|
45
|
+
"loading",
|
|
46
|
+
"feedback"
|
|
47
|
+
],
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=20.11.0"
|
|
53
|
+
},
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "git+https://github.com/obinexusmk2/obix-component-progress.git"
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @obinexusltd/obix-component-progress
|
|
3
|
+
*
|
|
4
|
+
* The ObixProgress feedback component — a `role="progressbar"` with a
|
|
5
|
+
* determinate value / min / max, or an `indeterminate` busy state, plus optional
|
|
6
|
+
* `aria-valuetext`.
|
|
7
|
+
*
|
|
8
|
+
* Data-Oriented: `createProgress(config)` returns
|
|
9
|
+
* `{ name, state, actions, render }`. Actions are pure
|
|
10
|
+
* `(state, …args) => ProgressState`; `render(state)` is deterministic,
|
|
11
|
+
* HTML-escaped markup. Zero dependencies.
|
|
12
|
+
*
|
|
13
|
+
* Split out of `@obinexusltd/obix-component-feedback`.
|
|
14
|
+
*/
|
|
15
|
+
import type { DOPComponent, ProgressConfig, ProgressState } from "./types.js";
|
|
16
|
+
|
|
17
|
+
export type { Action, DOPComponent, ProgressConfig, ProgressState } from "./types.js";
|
|
18
|
+
|
|
19
|
+
function esc(value: unknown): string {
|
|
20
|
+
return String(value ?? "")
|
|
21
|
+
.replace(/&/g, "&")
|
|
22
|
+
.replace(/</g, "<")
|
|
23
|
+
.replace(/>/g, ">")
|
|
24
|
+
.replace(/"/g, """);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function clamp(n: number, lo: number, hi: number): number {
|
|
28
|
+
return Math.min(hi, Math.max(lo, n));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function renderProgressHtml(state: ProgressState): string {
|
|
32
|
+
const span = state.max - state.min || 1;
|
|
33
|
+
const pct = state.indeterminate ? 0 : (clamp(state.value, state.min, state.max) - state.min) / span * 100;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
`<div class="obix-progress${state.indeterminate ? " obix-progress--indeterminate" : ""}"` +
|
|
37
|
+
` role="progressbar"` +
|
|
38
|
+
`${state.indeterminate ? "" : ` aria-valuenow="${state.value}"`}` +
|
|
39
|
+
` aria-valuemin="${state.min}" aria-valuemax="${state.max}"` +
|
|
40
|
+
` aria-label="${esc(state.label)}"` +
|
|
41
|
+
`${state.valueText ? ` aria-valuetext="${esc(state.valueText)}"` : ""}` +
|
|
42
|
+
`${state.indeterminate ? ' aria-busy="true"' : ""}>` +
|
|
43
|
+
`<div class="obix-progress__fill"${state.indeterminate ? "" : ` style="width:${pct}%;"`}></div>` +
|
|
44
|
+
`</div>`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function createProgress(config: ProgressConfig): DOPComponent<ProgressState> {
|
|
49
|
+
if (!config || typeof config.label !== "string") {
|
|
50
|
+
throw new TypeError("[obix-component-progress] createProgress: `label` is required");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const state: ProgressState = {
|
|
54
|
+
value: config.value ?? 0,
|
|
55
|
+
min: config.min ?? 0,
|
|
56
|
+
max: config.max ?? 100,
|
|
57
|
+
indeterminate: config.indeterminate ?? false,
|
|
58
|
+
label: config.label,
|
|
59
|
+
valueText: config.valueText ?? null,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const actions = {
|
|
63
|
+
setValue: (s: ProgressState, value: unknown): ProgressState => ({
|
|
64
|
+
...s,
|
|
65
|
+
value: Number(value),
|
|
66
|
+
indeterminate: false,
|
|
67
|
+
}),
|
|
68
|
+
setIndeterminate: (s: ProgressState, indeterminate: unknown): ProgressState => ({
|
|
69
|
+
...s,
|
|
70
|
+
indeterminate: Boolean(indeterminate),
|
|
71
|
+
}),
|
|
72
|
+
complete: (s: ProgressState): ProgressState => ({ ...s, value: s.max, indeterminate: false }),
|
|
73
|
+
reset: (s: ProgressState): ProgressState => ({ ...s, value: s.min }),
|
|
74
|
+
setValueText: (s: ProgressState, text: unknown): ProgressState => ({
|
|
75
|
+
...s,
|
|
76
|
+
valueText: String(text),
|
|
77
|
+
}),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return { name: "ObixProgress", state, actions, render: renderProgressHtml };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Render a progress bar's HTML in one call, optionally with state overrides. */
|
|
84
|
+
export function renderProgress(
|
|
85
|
+
config: ProgressConfig,
|
|
86
|
+
overrides: Partial<ProgressState> = {},
|
|
87
|
+
): string {
|
|
88
|
+
const progress = createProgress(config);
|
|
89
|
+
return progress.render({ ...progress.state, ...overrides });
|
|
90
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @obinexusltd/obix-component-progress — types.
|
|
3
|
+
* Zero-dependency Data-Oriented component: { name, state, actions, render }.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
7
|
+
|
|
8
|
+
export type Action<S> = (state: S, ...args: any[]) => S;
|
|
9
|
+
|
|
10
|
+
export interface DOPComponent<S> {
|
|
11
|
+
name: string;
|
|
12
|
+
state: S;
|
|
13
|
+
actions: Record<string, Action<S>>;
|
|
14
|
+
render: (state: S) => string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ProgressConfig {
|
|
18
|
+
/** Accessible name for the progress bar. Required. */
|
|
19
|
+
label: string;
|
|
20
|
+
value?: number;
|
|
21
|
+
min?: number;
|
|
22
|
+
max?: number;
|
|
23
|
+
/** Busy state with no known value — omits `aria-valuenow`, sets `aria-busy`. */
|
|
24
|
+
indeterminate?: boolean;
|
|
25
|
+
/** Human-friendly value string, e.g. `"3 of 10 files"` (`aria-valuetext`). */
|
|
26
|
+
valueText?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ProgressState {
|
|
30
|
+
value: number;
|
|
31
|
+
min: number;
|
|
32
|
+
max: number;
|
|
33
|
+
indeterminate: boolean;
|
|
34
|
+
label: string;
|
|
35
|
+
valueText: string | null;
|
|
36
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { createProgress, renderProgress } from "../dist/index.js";
|
|
4
|
+
|
|
5
|
+
test("factory returns a DOP component shape", () => {
|
|
6
|
+
const el = createProgress({ label: "Task" });
|
|
7
|
+
assert.equal(el.name, "ObixProgress");
|
|
8
|
+
assert.equal(typeof el.render, "function");
|
|
9
|
+
assert.equal(typeof el.actions, "object");
|
|
10
|
+
assert.equal(typeof el.render(el.state), "string");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("required config is validated", () => {
|
|
14
|
+
assert.throws(() => createProgress(), TypeError);
|
|
15
|
+
assert.throws(() => createProgress({}), TypeError);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("render is deterministic", () => {
|
|
19
|
+
assert.equal(renderProgress({ label: "Task" }), renderProgress({ label: "Task" }));
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("actions are pure — input state is untouched", () => {
|
|
23
|
+
const el = createProgress({ label: "Task" });
|
|
24
|
+
const before = JSON.stringify(el.state);
|
|
25
|
+
for (const k of Object.keys(el.actions)) el.actions[k](el.state, 1);
|
|
26
|
+
assert.equal(JSON.stringify(el.state), before);
|
|
27
|
+
});
|