@loadbare/widgets 0.10.1 → 0.11.1
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/README.md +17 -0
- package/dist-cli/package-root.js +24 -0
- package/dist-cli/skills-cli.js +87 -0
- package/dist-cli/skills.js +125 -0
- package/docs/lb-checkbox.md +5 -9
- package/docs/lb-form.md +2 -2
- package/docs/lb-input.md +4 -2
- package/docs/lb-master.md +2 -2
- package/docs/lb-options.md +2 -0
- package/docs/lb-select.md +2 -1
- package/docs/lb-table.md +4 -3
- package/docs/lb-unknown-page.md +2 -2
- package/package.json +11 -3
- package/skills/loadbare-widgets/SKILL.md +61 -0
- package/skills/loadbare-widgets/references/lb-checkbox.md +59 -0
- package/skills/loadbare-widgets/references/lb-confirm.md +44 -0
- package/skills/loadbare-widgets/references/lb-date.md +48 -0
- package/skills/loadbare-widgets/references/lb-form.md +35 -0
- package/skills/loadbare-widgets/references/lb-input.md +77 -0
- package/skills/loadbare-widgets/references/lb-insert-dialog.md +73 -0
- package/skills/loadbare-widgets/references/lb-mailto.md +29 -0
- package/skills/loadbare-widgets/references/lb-master.md +65 -0
- package/skills/loadbare-widgets/references/lb-number.md +53 -0
- package/skills/loadbare-widgets/references/lb-options.md +113 -0
- package/skills/loadbare-widgets/references/lb-picker.md +53 -0
- package/skills/loadbare-widgets/references/lb-select.md +70 -0
- package/skills/loadbare-widgets/references/lb-table.md +173 -0
- package/skills/loadbare-widgets/references/lb-tel.md +32 -0
- package/skills/loadbare-widgets/references/lb-unknown-page.md +43 -0
package/README.md
CHANGED
|
@@ -41,3 +41,20 @@ Apart from the line in `src/imports.ts`, no other import statements
|
|
|
41
41
|
are required. The loadbare builder automatically bundles the HTML and
|
|
42
42
|
JS for any widget in the library that is actually used in the app.
|
|
43
43
|
|
|
44
|
+
## Agent skills
|
|
45
|
+
|
|
46
|
+
The package ships an agent skill, `loadbare-widgets`, written for a coding agent working
|
|
47
|
+
in a project that depends on `@loadbare/widgets`. Install it with:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx loadbare-widgets skills install
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
It links the skill into `.agents/skills/` and `.claude/skills/`, where agents
|
|
54
|
+
look for skills. `--copy` writes copies instead of links, and `--dir <path>`
|
|
55
|
+
installs into one directory of your choosing. The command prints the paths to
|
|
56
|
+
add to `.gitignore` and writes nothing there itself.
|
|
57
|
+
|
|
58
|
+
`npx loadbare-app skills install`, `npx loadbare-widgets skills install` and
|
|
59
|
+
`npx loadbare-db skills install` install the three loadbare skills, one per
|
|
60
|
+
package.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walk up from `dir` to the nearest ancestor holding a package.json.
|
|
3
|
+
*
|
|
4
|
+
* Needed because a caller may run from source (cli/*.ts) or compiled
|
|
5
|
+
* (dist-cli/*.js) — both are the same distance from the package root, but
|
|
6
|
+
* nothing here should assume that stays true.
|
|
7
|
+
*/
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
export async function findPackageRoot(dir) {
|
|
11
|
+
for (;;) {
|
|
12
|
+
try {
|
|
13
|
+
await readFile(path.join(dir, "package.json"), "utf-8");
|
|
14
|
+
return dir;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
const parent = path.dirname(dir);
|
|
18
|
+
if (parent === dir) {
|
|
19
|
+
throw new Error(`could not find a package.json above '${dir}'`);
|
|
20
|
+
}
|
|
21
|
+
dir = parent;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* loadbare-widgets — the package's one command.
|
|
4
|
+
*
|
|
5
|
+
* loadbare-widgets skills install [--copy] [--dir <path>]
|
|
6
|
+
*
|
|
7
|
+
* Installs the agent skills that ship in this package into `.agents/skills`
|
|
8
|
+
* and `.claude/skills`, or into `--dir` alone — see cli/skills.ts. Nothing
|
|
9
|
+
* goes to stdout but the usage `--help` asks for; every other line is
|
|
10
|
+
* progress, on stderr.
|
|
11
|
+
*/
|
|
12
|
+
import { parseArgs } from "node:util";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { findPackageRoot } from "./package-root.js";
|
|
16
|
+
import { installSkills, SKILL_DESTINATIONS } from "./skills.js";
|
|
17
|
+
const USAGE = "usage: loadbare-widgets skills install [--copy] [--dir <path>]";
|
|
18
|
+
async function main() {
|
|
19
|
+
let parsed;
|
|
20
|
+
try {
|
|
21
|
+
parsed = parseArgs({
|
|
22
|
+
allowPositionals: true,
|
|
23
|
+
options: {
|
|
24
|
+
copy: { type: "boolean", default: false },
|
|
25
|
+
dir: { type: "string" },
|
|
26
|
+
help: { type: "boolean", short: "h", default: false },
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
console.error(`loadbare-widgets: ${err.message}`);
|
|
32
|
+
console.error(USAGE);
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
const { values, positionals } = parsed;
|
|
36
|
+
// Asked for, the usage is the product, so it alone goes to stdout.
|
|
37
|
+
if (values.help) {
|
|
38
|
+
console.log(USAGE);
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
if (positionals.join(" ") !== "skills install") {
|
|
42
|
+
console.error(USAGE);
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
// Source and compiled output sit at different depths, so the package is
|
|
46
|
+
// found by walking up rather than by counting.
|
|
47
|
+
const packageRoot = await findPackageRoot(path.dirname(fileURLToPath(import.meta.url)));
|
|
48
|
+
const source = path.join(packageRoot, "skills");
|
|
49
|
+
const destinations = values.dir ? [values.dir] : SKILL_DESTINATIONS;
|
|
50
|
+
const result = installSkills({
|
|
51
|
+
source,
|
|
52
|
+
destinations,
|
|
53
|
+
copy: values.copy,
|
|
54
|
+
cwd: process.cwd(),
|
|
55
|
+
log: (line) => console.error(line),
|
|
56
|
+
});
|
|
57
|
+
// An install with no skills in it is a packaging fault, not a user error,
|
|
58
|
+
// and saying where it looked is what makes that diagnosable.
|
|
59
|
+
if (result.names.length === 0) {
|
|
60
|
+
console.error(`loadbare-widgets: this install of @loadbare/widgets carries no skills (looked in ${source})`);
|
|
61
|
+
return 1;
|
|
62
|
+
}
|
|
63
|
+
console.error("");
|
|
64
|
+
if (result.copiedInstead) {
|
|
65
|
+
console.error("Some links could not be created and those skills were copied instead.");
|
|
66
|
+
console.error("A copy does not follow the package when it is upgraded — rerun this");
|
|
67
|
+
console.error("command after an upgrade, or use --copy everywhere and commit the result.");
|
|
68
|
+
console.error("");
|
|
69
|
+
}
|
|
70
|
+
if (values.copy) {
|
|
71
|
+
console.error("These are copies. Commit them, and rerun this command after upgrading");
|
|
72
|
+
console.error("@loadbare/widgets so the skill matches the widgets.");
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.error("These are links into node_modules. To keep them out of git:");
|
|
76
|
+
console.error("");
|
|
77
|
+
for (const destination of destinations) {
|
|
78
|
+
for (const name of result.names) {
|
|
79
|
+
console.error(` ${path.join(destination, name)}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
console.error("");
|
|
84
|
+
console.error("Start your agent from this directory and it will find them.");
|
|
85
|
+
return result.refused > 0 ? 1 : 0;
|
|
86
|
+
}
|
|
87
|
+
process.exitCode = await main();
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Put the agent skills that ship in this package where a coding agent will
|
|
3
|
+
* find them.
|
|
4
|
+
*
|
|
5
|
+
* The same command as `loadbare-db skills install` and `loadbare-app skills
|
|
6
|
+
* install`, and deliberately a copy of them rather than a shared module:
|
|
7
|
+
* this package reaches `@loadbare/app` only through `constants` and `types`,
|
|
8
|
+
* and a skill installer is neither. The copies are kept identical, so a
|
|
9
|
+
* change to one belongs in all three.
|
|
10
|
+
*
|
|
11
|
+
* A symlink by default, because the point is that the skill tracks the
|
|
12
|
+
* dependency: `npm install @loadbare/widgets@next` and the skill moves with it.
|
|
13
|
+
* `copy` is for a team that would rather commit the text than have every
|
|
14
|
+
* clone depend on `node_modules` being populated.
|
|
15
|
+
*
|
|
16
|
+
* Nothing is done to `.gitignore`. Whether these paths are committed is a
|
|
17
|
+
* decision about the project, and the command that installs a file is not the
|
|
18
|
+
* one to make it.
|
|
19
|
+
*/
|
|
20
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmSync, symlinkSync, } from "node:fs";
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
/**
|
|
23
|
+
* Where a coding agent looks for skills.
|
|
24
|
+
*
|
|
25
|
+
* `.agents/skills` is the vendor-neutral location, read by Codex, Cursor and
|
|
26
|
+
* Copilot; `.claude/skills` is Claude Code's, and Cursor reads it too. Both
|
|
27
|
+
* are written rather than guessing from what happens to be configured.
|
|
28
|
+
*/
|
|
29
|
+
export const SKILL_DESTINATIONS = [".agents/skills", ".claude/skills"];
|
|
30
|
+
/** `absent` covers a dangling symlink, which `existsSync` reports as missing. */
|
|
31
|
+
function entryKind(p) {
|
|
32
|
+
let stat;
|
|
33
|
+
try {
|
|
34
|
+
stat = lstatSync(p);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return "absent";
|
|
38
|
+
}
|
|
39
|
+
if (stat.isSymbolicLink())
|
|
40
|
+
return "symlink";
|
|
41
|
+
if (stat.isDirectory())
|
|
42
|
+
return "directory";
|
|
43
|
+
return "other";
|
|
44
|
+
}
|
|
45
|
+
/** The `name:` a skill directory declares, or null if there is no skill there. */
|
|
46
|
+
function declaredSkillName(dir) {
|
|
47
|
+
try {
|
|
48
|
+
const match = /^name:\s*(\S+)\s*$/m.exec(readFileSync(path.join(dir, "SKILL.md"), "utf-8"));
|
|
49
|
+
return match?.[1] ?? null;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A link into `node_modules` is relative, so the project stays movable and the
|
|
57
|
+
* link means the same thing to everyone who checks it out. A globally
|
|
58
|
+
* installed package gets an absolute path, since a relative one breaks the
|
|
59
|
+
* moment the project moves.
|
|
60
|
+
*/
|
|
61
|
+
function linkTarget(from, to, cwd) {
|
|
62
|
+
const inside = path.resolve(from).startsWith(path.resolve(cwd) + path.sep);
|
|
63
|
+
return inside
|
|
64
|
+
? path.relative(path.dirname(path.resolve(cwd, to)), path.resolve(from))
|
|
65
|
+
: path.resolve(from);
|
|
66
|
+
}
|
|
67
|
+
/** The skills under `source`: each directory holding a `SKILL.md`. */
|
|
68
|
+
export function shippedSkills(source) {
|
|
69
|
+
if (!existsSync(source))
|
|
70
|
+
return [];
|
|
71
|
+
return readdirSync(source, { withFileTypes: true })
|
|
72
|
+
.filter((entry) => entry.isDirectory() &&
|
|
73
|
+
existsSync(path.join(source, entry.name, "SKILL.md")))
|
|
74
|
+
.map((entry) => entry.name)
|
|
75
|
+
.sort();
|
|
76
|
+
}
|
|
77
|
+
export function installSkills(options) {
|
|
78
|
+
const { source, destinations, copy, cwd, log } = options;
|
|
79
|
+
const names = shippedSkills(source);
|
|
80
|
+
let refused = 0;
|
|
81
|
+
let copiedInstead = false;
|
|
82
|
+
for (const destination of destinations) {
|
|
83
|
+
for (const name of names) {
|
|
84
|
+
const from = path.join(source, name);
|
|
85
|
+
const to = path.join(destination, name);
|
|
86
|
+
const at = path.resolve(cwd, to);
|
|
87
|
+
const kind = entryKind(at);
|
|
88
|
+
// A directory this command wrote says so on its own front matter, and
|
|
89
|
+
// is replaced. Anything else at that path belongs to someone, and a
|
|
90
|
+
// skill install is not permitted to be the thing that lost it.
|
|
91
|
+
if (kind === "directory" && declaredSkillName(at) !== name) {
|
|
92
|
+
log(` refused ${to} — a directory is already there and is not the ${name} skill`);
|
|
93
|
+
refused++;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (kind === "other") {
|
|
97
|
+
log(` refused ${to} — a file is already there`);
|
|
98
|
+
refused++;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
mkdirSync(path.resolve(cwd, destination), { recursive: true });
|
|
102
|
+
rmSync(at, { recursive: true, force: true });
|
|
103
|
+
if (copy) {
|
|
104
|
+
cpSync(from, at, { recursive: true });
|
|
105
|
+
log(` copied ${to}`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
symlinkSync(linkTarget(from, to, cwd), at, "dir");
|
|
110
|
+
log(` linked ${to}`);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
// Windows refuses a symlink to an unprivileged process unless
|
|
114
|
+
// developer mode is on. A copy is the same skill, so the install
|
|
115
|
+
// succeeds and the difference is reported rather than raised.
|
|
116
|
+
if (error.code !== "EPERM")
|
|
117
|
+
throw error;
|
|
118
|
+
cpSync(from, at, { recursive: true });
|
|
119
|
+
copiedInstead = true;
|
|
120
|
+
log(` copied ${to}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { names, refused, copiedInstead };
|
|
125
|
+
}
|
package/docs/lb-checkbox.md
CHANGED
|
@@ -33,21 +33,17 @@ so they are expansion attributes here rather than `lb-column` and
|
|
|
33
33
|
## Why it is not like `lb-input`
|
|
34
34
|
|
|
35
35
|
`lb-input` has a script that makes the tag itself the control, so the page
|
|
36
|
-
writes `lb-column` on the tag. `lb-checkbox`
|
|
37
|
-
|
|
38
|
-
`lb-column-value` stamp, a string, and a checkbox's state is a boolean. A
|
|
39
|
-
script `lb-checkbox` would read the stamp `"false"` as checked, since no
|
|
40
|
-
string is read. The native checkbox has no such gap: the hub sets `checked`
|
|
41
|
-
on it directly.
|
|
36
|
+
writes `lb-column` on the tag. `lb-checkbox` is markup alone, and the hub
|
|
37
|
+
sets `checked` on the native checkbox inside it directly.
|
|
42
38
|
|
|
43
39
|
`lb-input` could equally be markup alone; its script is a convention, kept
|
|
44
40
|
for now.
|
|
45
41
|
|
|
46
42
|
## The control
|
|
47
43
|
|
|
48
|
-
The checkbox is checked when its column is anything but `false
|
|
49
|
-
sends `"true"` or `"false"`. See
|
|
50
|
-
|
|
44
|
+
The checkbox is checked when its column is anything but `false`, null,
|
|
45
|
+
`"false"` or `""`, and sends `"true"` or `"false"`. See "Checkboxes" in the data-binding reference
|
|
46
|
+
of the loadbare-app skill.
|
|
51
47
|
|
|
52
48
|
## Styling
|
|
53
49
|
|
package/docs/lb-form.md
CHANGED
|
@@ -26,8 +26,8 @@ Requires `exp-id`.
|
|
|
26
26
|
A row with a key is on record, and each control saves its own column on
|
|
27
27
|
`change` with `lb-request="lb-row-update"`. A row whose key is empty is a new
|
|
28
28
|
one not yet entered: the hub sends no update for it, so the controls only
|
|
29
|
-
hold what a submit will gather. See
|
|
30
|
-
|
|
29
|
+
hold what a submit will gather. See "A row not yet entered" in the
|
|
30
|
+
data-binding reference of the loadbare-app skill.
|
|
31
31
|
|
|
32
32
|
A definition only, with no script of its own.
|
|
33
33
|
|
package/docs/lb-input.md
CHANGED
|
@@ -43,13 +43,15 @@ off drops the attribute.
|
|
|
43
43
|
`lb-input` is a form-associated custom element. Its `value` property gets and
|
|
44
44
|
sets the value of the `<input>` inside it, and it fires `change` on itself when
|
|
45
45
|
that `<input>` fires `change`. A form reset returns the `<input>` to its
|
|
46
|
-
default.
|
|
46
|
+
default. A refusal's message for its column goes to the `<input>`, as HTML's
|
|
47
|
+
own validity.
|
|
47
48
|
|
|
48
49
|
## Inside a form
|
|
49
50
|
|
|
50
51
|
An `lb-input` inside an `lb-row-insert` or `lb-row-update` form carries no
|
|
51
52
|
`lb-request`. The hub gathers every `lb-column` whose form owner is the form
|
|
52
|
-
on submit and sends one request for all of them
|
|
53
|
+
on submit and sends one request for all of them, so a widget that also sent
|
|
54
|
+
its own would write the same edit twice.
|
|
53
55
|
|
|
54
56
|
```html
|
|
55
57
|
<form lb-query="people" lb-request="lb-row-insert">
|
package/docs/lb-master.md
CHANGED
|
@@ -46,8 +46,8 @@ Requires `exp-row`.
|
|
|
46
46
|
| Cancel | A new row | Resets the form |
|
|
47
47
|
|
|
48
48
|
Each carries `data-mode`, `on-record` or `new`, and the stylesheet shows each
|
|
49
|
-
only in its mode. A new row is one whose key is empty, a
|
|
50
|
-
|
|
49
|
+
only in its mode. A new row is one whose key is empty, a row not yet entered
|
|
50
|
+
(see that section in the data-binding reference of the loadbare-app skill).
|
|
51
51
|
|
|
52
52
|
Which row shows is a query parm, and only the page knows its name, so New is
|
|
53
53
|
a request the page declares. Its handler empties the parm, and the row
|
package/docs/lb-options.md
CHANGED
|
@@ -80,6 +80,8 @@ and `disabled` so it is never a choice.
|
|
|
80
80
|
`lb-options` is a form-associated custom element. Its `value` property gets
|
|
81
81
|
and sets the value of the `<select>` inside it, and it fires `change` on itself
|
|
82
82
|
when that `<select>` fires `change`. A form reset selects the first option.
|
|
83
|
+
A refusal's message for its column goes to the `<select>`, as HTML's own
|
|
84
|
+
validity.
|
|
83
85
|
|
|
84
86
|
The hub reads the element's own `lb-column` from the row around it, and lands
|
|
85
87
|
the rows of its `lb-query` inside it. A value set before the option it names
|
package/docs/lb-select.md
CHANGED
|
@@ -37,7 +37,8 @@ improvements through iterative use.
|
|
|
37
37
|
`lb-select` is a form-associated custom element. Its `value` property gets
|
|
38
38
|
and sets the value of the `<select>` inside it, and it fires `change` on itself
|
|
39
39
|
when that `<select>` fires `change`. A form reset selects the option marked
|
|
40
|
-
`selected`, or the first option.
|
|
40
|
+
`selected`, or the first option. A refusal's message for its column goes to
|
|
41
|
+
the `<select>`, as HTML's own validity.
|
|
41
42
|
|
|
42
43
|
## Choosing
|
|
43
44
|
|
package/docs/lb-table.md
CHANGED
|
@@ -72,8 +72,9 @@ filter does not pass is hidden.
|
|
|
72
72
|
## The order on the heading
|
|
73
73
|
|
|
74
74
|
The heading of the column the rows are sorted by carries a triangle, up for
|
|
75
|
-
ascending and down for descending, and `aria-sort
|
|
76
|
-
|
|
75
|
+
ascending and down for descending, and `aria-sort`, read from the
|
|
76
|
+
`lb-query-order` the hub stamps on the table. Only the order's leading term
|
|
77
|
+
is shown. A heading's column is the `lb-column` in the cell
|
|
77
78
|
at the same position in the row template, on the cell or inside it. A cell
|
|
78
79
|
naming no column, or several, gives its heading none.
|
|
79
80
|
|
|
@@ -160,7 +161,7 @@ created, or moved by changing a column the order reads.
|
|
|
160
161
|
|
|
161
162
|
The footer is an ordinary part of the page, not a row from the table's query.
|
|
162
163
|
An aggregate there covers every row showing. A figure that is not a sum of the rows,
|
|
163
|
-
such as a budget, is
|
|
164
|
+
such as a budget, is a `<tr>` with its own `lb-query`, a query of kind
|
|
164
165
|
`row` declared on the server like any other.
|
|
165
166
|
|
|
166
167
|
## Styling an empty table
|
package/docs/lb-unknown-page.md
CHANGED
|
@@ -39,5 +39,5 @@ there is no page label to show.
|
|
|
39
39
|
## Inside the hub
|
|
40
40
|
|
|
41
41
|
The hub finds the dialog inside its own subtree and nowhere else, so the tag
|
|
42
|
-
goes inside `<lb-hub>`.
|
|
43
|
-
|
|
42
|
+
goes inside `<lb-hub>`. The chrome reference of the loadbare-app skill shows
|
|
43
|
+
the same dialog written by hand in `chrome.html`.
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loadbare/widgets",
|
|
3
3
|
"description": "The basic widget set for @loadbare/app: input, select, options, picker, table, unknown-page, confirm, form, insert-dialog, master, checkbox, number, date, tel, mailto",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.11.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
8
|
+
"dist-cli",
|
|
8
9
|
"docs",
|
|
10
|
+
"skills/loadbare-widgets/SKILL.md",
|
|
11
|
+
"skills/loadbare-widgets/references",
|
|
9
12
|
"README.md"
|
|
10
13
|
],
|
|
11
14
|
"publishConfig": {
|
|
@@ -17,12 +20,17 @@
|
|
|
17
20
|
"default": "./dist/index.js"
|
|
18
21
|
}
|
|
19
22
|
},
|
|
23
|
+
"bin": {
|
|
24
|
+
"loadbare-widgets": "dist-cli/skills-cli.js"
|
|
25
|
+
},
|
|
20
26
|
"loadbare": {
|
|
21
27
|
"widgets": "./dist"
|
|
22
28
|
},
|
|
23
29
|
"scripts": {
|
|
24
|
-
"build": "node scripts/build.mjs",
|
|
25
|
-
"
|
|
30
|
+
"build": "node scripts/build.mjs && npm run build:cli && npm run build:skills",
|
|
31
|
+
"build:cli": "rm -rf dist-cli && tsc --project tsconfig.cli.json",
|
|
32
|
+
"build:skills": "node scripts/build-skills.mjs",
|
|
33
|
+
"postbuild": "node --eval \"fs.chmodSync('dist-cli/skills-cli.js', 0o755)\" --input-type=module && node ../../scripts/check-dist-imports.mjs",
|
|
26
34
|
"test": "tsx --test \"tests/**/*.test.ts\"",
|
|
27
35
|
"typecheck": "tsc --noEmit",
|
|
28
36
|
"format": "prettier --write .",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: loadbare-widgets
|
|
3
|
+
description: The basic widget set for Loadbare/app, shipped as `@loadbare/widgets`. Covers each widget's parameters, the attributes it reads, and what a change sends; `lb-input`, `lb-select`, `lb-options`, `lb-picker`, `lb-table`, `lb-unknown-page`, `lb-confirm`, `lb-form`, `lb-insert-dialog`, `lb-master`, `lb-checkbox`, `lb-number`, `lb-date`, `lb-tel` and `lb-mailto`. Use whenever a task writes or changes markup using one of these tags, or decides whether a page needs a custom element of its own. The pages, queries and requests the widgets sit in are the loadbare-app skill's.
|
|
4
|
+
license: Apache-2.0
|
|
5
|
+
metadata:
|
|
6
|
+
package: "@loadbare/widgets"
|
|
7
|
+
homepage: https://gitlab.com/kendowns/loadbare
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Working with @loadbare/widgets
|
|
11
|
+
|
|
12
|
+
Every widget is an ordinary custom element for `@loadbare/app`: markup with
|
|
13
|
+
`lb-*` attributes and `exp-*` parameters, landed and gathered by the hub like
|
|
14
|
+
any other element. How pages, queries, requests and custom elements work is
|
|
15
|
+
in the loadbare-app skill. This skill covers only what each widget takes and
|
|
16
|
+
does.
|
|
17
|
+
|
|
18
|
+
Install the package and list it in the application's `src/imports.ts`:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
export default ["@loadbare/widgets"];
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The builder then bundles the definition and script of each widget a page
|
|
25
|
+
uses, and nothing else. The application's own definition of a tag
|
|
26
|
+
overrides the package's.
|
|
27
|
+
|
|
28
|
+
`lb-input`, `lb-select`, `lb-options` and `lb-picker` are controls:
|
|
29
|
+
form-associated custom elements with a `value` property that fire `change`.
|
|
30
|
+
Each carries `lb-column` and `lb-request` itself, the way an `<input>` does.
|
|
31
|
+
Inside a form that sends one request for all its controls, leave
|
|
32
|
+
`lb-request` off.
|
|
33
|
+
|
|
34
|
+
## Reference material
|
|
35
|
+
|
|
36
|
+
One page per widget. They ship with the package, so no network is needed.
|
|
37
|
+
|
|
38
|
+
| Widget | Is |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| [`lb-input`](references/lb-input.md) | a control wrapping an `<input>` |
|
|
41
|
+
| [`lb-select`](references/lb-select.md) | a control wrapping a `<select>` whose options the page writes |
|
|
42
|
+
| [`lb-options`](references/lb-options.md) | a control wrapping a `<select>` whose options come from a query |
|
|
43
|
+
| [`lb-picker`](references/lb-picker.md) | `lb-options` with the row template built in |
|
|
44
|
+
| [`lb-table`](references/lb-table.md) | a `<table>` supplying its own scaffolding and a ghost row |
|
|
45
|
+
| [`lb-unknown-page`](references/lb-unknown-page.md) | the chrome's dialog for a URL that names no page |
|
|
46
|
+
| [`lb-confirm`](references/lb-confirm.md) | a button that asks before it sends |
|
|
47
|
+
| [`lb-form`](references/lb-form.md) | a form over one row, which saves per control once the row is on record |
|
|
48
|
+
| [`lb-insert-dialog`](references/lb-insert-dialog.md) | a row entered in a modal dialog, chosen in the picker that opened it |
|
|
49
|
+
| [`lb-master`](references/lb-master.md) | a set, one of its rows, and the buttons that act on the set |
|
|
50
|
+
| [`lb-checkbox`](references/lb-checkbox.md) | a checkbox and its label, as markup to style |
|
|
51
|
+
| [`lb-number`](references/lb-number.md) | a number shown by `Intl.NumberFormat` |
|
|
52
|
+
| [`lb-date`](references/lb-date.md) | a date or time shown by `Intl.DateTimeFormat` |
|
|
53
|
+
| [`lb-tel`](references/lb-tel.md) | a phone number shown as a `tel:` link |
|
|
54
|
+
| [`lb-mailto`](references/lb-mailto.md) | an email address shown as a `mailto:` link |
|
|
55
|
+
|
|
56
|
+
## The other loadbare skills
|
|
57
|
+
|
|
58
|
+
`npx loadbare-widgets skills install` puts this skill where a coding agent
|
|
59
|
+
finds it. `npx loadbare-app skills install` and
|
|
60
|
+
`npx loadbare-db skills install` do the same for the loadbare-app and
|
|
61
|
+
loadbare-db skills.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# `lb-checkbox`
|
|
2
|
+
|
|
3
|
+
> Status: a barebones starter. Iterative use will drive improvements.
|
|
4
|
+
|
|
5
|
+
A checkbox and its label as one element, for an application to style. A
|
|
6
|
+
definition only, with no script: the hub treats a native checkbox as a
|
|
7
|
+
control, so the widget adds nothing but the markup.
|
|
8
|
+
|
|
9
|
+
Requires `exp-column` and `exp-label`.
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<div lb-query="people">
|
|
13
|
+
<template>
|
|
14
|
+
<div>
|
|
15
|
+
<lb-checkbox exp-column="active" exp-label="Active" exp-request="lb-row-update"></lb-checkbox>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
</div>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Expansion Attributes
|
|
22
|
+
|
|
23
|
+
| Parameter | Fills |
|
|
24
|
+
| ------------- | ----------------------------------------------- |
|
|
25
|
+
| `exp-column` | the checkbox's `lb-column` |
|
|
26
|
+
| `exp-label` | the label's text |
|
|
27
|
+
| `exp-request` | the checkbox's `lb-request`, none when absent |
|
|
28
|
+
|
|
29
|
+
The column and request go on the checkbox inside, since it is the control,
|
|
30
|
+
so they are expansion attributes here rather than `lb-column` and
|
|
31
|
+
`lb-request` on the tag.
|
|
32
|
+
|
|
33
|
+
## Why it is not like `lb-input`
|
|
34
|
+
|
|
35
|
+
`lb-input` has a script that makes the tag itself the control, so the page
|
|
36
|
+
writes `lb-column` on the tag. `lb-checkbox` is markup alone, and the hub
|
|
37
|
+
sets `checked` on the native checkbox inside it directly.
|
|
38
|
+
|
|
39
|
+
`lb-input` could equally be markup alone; its script is a convention, kept
|
|
40
|
+
for now.
|
|
41
|
+
|
|
42
|
+
## The control
|
|
43
|
+
|
|
44
|
+
The checkbox is checked when its column is anything but `false`, null,
|
|
45
|
+
`"false"` or `""`, and sends `"true"` or `"false"`. See "Checkboxes" in the data-binding reference
|
|
46
|
+
of the loadbare-app skill.
|
|
47
|
+
|
|
48
|
+
## Styling
|
|
49
|
+
|
|
50
|
+
The definition is a `<label>` holding the `<input type="checkbox">` and a
|
|
51
|
+
`<span>` with the text:
|
|
52
|
+
|
|
53
|
+
```css
|
|
54
|
+
lb-checkbox label {
|
|
55
|
+
display: inline-flex;
|
|
56
|
+
gap: 0.5em;
|
|
57
|
+
align-items: center;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# `lb-confirm`
|
|
2
|
+
|
|
3
|
+
> Status: a barebones starter. Iterative use will drive improvements.
|
|
4
|
+
|
|
5
|
+
A button that asks before it sends. The button opens a dialog holding the
|
|
6
|
+
message the page writes inside the tag, a Cancel button, and a confirm button
|
|
7
|
+
carrying the request.
|
|
8
|
+
|
|
9
|
+
Requires `exp-label` and `exp-request`.
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<template>
|
|
13
|
+
<tr>
|
|
14
|
+
<td lb-column="name"></td>
|
|
15
|
+
<td>
|
|
16
|
+
<lb-confirm exp-label="Delete" exp-request="lb-row-delete">
|
|
17
|
+
Delete <b lb-column="name"></b>?
|
|
18
|
+
</lb-confirm>
|
|
19
|
+
</td>
|
|
20
|
+
</tr>
|
|
21
|
+
</template>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Expansion Attributes
|
|
25
|
+
|
|
26
|
+
| Parameter | Fills |
|
|
27
|
+
| ------------- | --------------------------------------------- |
|
|
28
|
+
| `exp-label` | the text of the button that opens the dialog |
|
|
29
|
+
| `exp-request` | the confirm button's `lb-request` |
|
|
30
|
+
| `exp-confirm` | the confirm button's text, `Confirm` when absent |
|
|
31
|
+
|
|
32
|
+
## The request
|
|
33
|
+
|
|
34
|
+
The request is on the dialog's confirm button, never on the tag, so nothing
|
|
35
|
+
reaches the hub until the user says yes. The dialog sits in the row the tag
|
|
36
|
+
sits in, so the request carries that row's query and key, and the message
|
|
37
|
+
can show the row's columns with `lb-column`.
|
|
38
|
+
|
|
39
|
+
Cancel comes first and takes focus, so Enter on a dialog opened by accident
|
|
40
|
+
sends nothing. The widget closes the dialog when the request succeeds. A refused request leaves it open, with the hub's
|
|
41
|
+
`lb-request-error` on the confirm button.
|
|
42
|
+
|
|
43
|
+
The dialog is found by its place in the tag, not by id, so the tag works in a
|
|
44
|
+
row template, where the hub clones it once per row.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# `lb-date`
|
|
2
|
+
|
|
3
|
+
> Status: a barebones starter. Iterative use will drive improvements.
|
|
4
|
+
|
|
5
|
+
A date or a time shown by the browser's `Intl.DateTimeFormat`. Not a
|
|
6
|
+
control: write `lb-column` on the tag, and the hub lands the column on it
|
|
7
|
+
and never gathers it.
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<lb-date lb-column="due" exp-date-style="medium"></lb-date>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Expansion Attributes
|
|
14
|
+
|
|
15
|
+
Each parameter is one of `Intl.DateTimeFormat`'s options, in kebab-case. A
|
|
16
|
+
parameter not given leaves Intl's default.
|
|
17
|
+
|
|
18
|
+
| Parameter | `Intl.DateTimeFormat` option |
|
|
19
|
+
| -------------------- | ------------------------------------- |
|
|
20
|
+
| `exp-locale` | the locale, the browser's when absent |
|
|
21
|
+
| `exp-date-style` | `dateStyle` |
|
|
22
|
+
| `exp-time-style` | `timeStyle` |
|
|
23
|
+
| `exp-time-zone` | `timeZone` |
|
|
24
|
+
| `exp-time-zone-name` | `timeZoneName` |
|
|
25
|
+
| `exp-hour-cycle` | `hourCycle` |
|
|
26
|
+
| `exp-weekday` | `weekday` |
|
|
27
|
+
| `exp-era` | `era` |
|
|
28
|
+
| `exp-year` | `year` |
|
|
29
|
+
| `exp-month` | `month` |
|
|
30
|
+
| `exp-day` | `day` |
|
|
31
|
+
| `exp-hour` | `hour` |
|
|
32
|
+
| `exp-minute` | `minute` |
|
|
33
|
+
| `exp-second` | `second` |
|
|
34
|
+
|
|
35
|
+
Intl refuses `exp-date-style` or `exp-time-style` beside any of
|
|
36
|
+
`exp-weekday` through `exp-second`.
|
|
37
|
+
|
|
38
|
+
## The value
|
|
39
|
+
|
|
40
|
+
The widget reads the value as JavaScript's `Date` does. A date alone, as
|
|
41
|
+
`YYYY-MM-DD`, shows as that day in every time zone. A value that is no
|
|
42
|
+
date shows as it arrived. Null shows nothing.
|
|
43
|
+
|
|
44
|
+
## Styling
|
|
45
|
+
|
|
46
|
+
The definition is a `<time>` whose text is the formatted date and whose
|
|
47
|
+
`datetime` is the value as it arrived. The hub stamps the tag with
|
|
48
|
+
`lb-column-value`, the value as it arrived.
|