@usableapp/cardds 0.7.98 → 0.7.100
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/CHANGELOG.md +22 -0
- package/bin/cardds.mjs +2 -2
- package/dist/catalog.json +1 -1
- package/docs/reference.md +6 -0
- package/lint/rules.mjs +29 -3
- package/package.json +1 -1
package/docs/reference.md
CHANGED
|
@@ -575,6 +575,12 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
575
575
|
them. Seen in timebank's help-ask prototype: a Composer, a Dropdown and a
|
|
576
576
|
map on white cards inside the help sheet, the cards the colour of the
|
|
577
577
|
ground.
|
|
578
|
+
`field-off-sheet` (error) flags the same controls when their nearest
|
|
579
|
+
container in the file is a `Page`, `Pages`, `Step` or `Screen`: a form is
|
|
580
|
+
never on the ground. A form over a screen is a `Sheet` in a
|
|
581
|
+
`SheetStage over dim` (gallery `Home/AskSheet`). A bar's control is not a
|
|
582
|
+
form on the ground: a `Slider` in an `ActionBarTier`, a `Dropdown` in a
|
|
583
|
+
`FilterRow` or a `TopBar`'s actions, a `Toggle` in an `AppBar`.
|
|
578
584
|
- **Two sheet systems, one material.** *Sheet single* (`.sheet`, `sheet.css`)
|
|
579
585
|
is ONE sheet the member acts in, whatever it holds: a short entry in front
|
|
580
586
|
of a step's card, a place on a map, a payment on a dim page — in a `.step`
|
package/lint/rules.mjs
CHANGED
|
@@ -119,7 +119,8 @@ function* styleEntries(body, at, sep) {
|
|
|
119
119
|
export function checkSource(source, file, { classes = carddsClasses() } = {}) {
|
|
120
120
|
const findings = [];
|
|
121
121
|
const lines = source.split('\n');
|
|
122
|
-
|
|
122
|
+
// comments out, lines kept; a string is stepped over whole, so `accept="image/*"` opens no comment
|
|
123
|
+
let text = source.replace(/"[^"\n]*"|'[^'\n]*'|`[^`]*`|\/\*[\s\S]*?\*\/|(?<![:'"`\\])\/\/[^\n]*/g, (c) => /^["'`]/.test(c) ? c : c.replace(/[^\n]/g, ' '));
|
|
123
124
|
const add = (rule, level, idx, message) => {
|
|
124
125
|
const line = lineOf(text, idx);
|
|
125
126
|
if (!/cardds-ignore-next-line/.test(lines[line - 2] ?? '')) findings.push({ rule, level, file, line, message }); // `// cardds-ignore-next-line` — a deliberate case (a story that shows the wrong way)
|
|
@@ -229,15 +230,40 @@ export function checkSource(source, file, { classes = carddsClasses() } = {}) {
|
|
|
229
230
|
|
|
230
231
|
// 10 · the sheet is the paper the member fills in (Lh 2026-10-18): every control the member fills or picks stands on a Sheet's body, a SheetStep
|
|
231
232
|
// or a stack sheet; a card is for reading and pressing buttons. A Card that is a sheet of a SheetStack IS a sheet (the stack's material)
|
|
232
|
-
|
|
233
|
+
// a `<` right after a name is a type argument (`Record<Step, string>`, `useState<Step>`), not a tag; an element that never closes holds nothing
|
|
234
|
+
const span = (re) => [...text.matchAll(re)].filter((m) => !/[\w$)\].]/.test(text[m.index - 1] ?? '')).map((m) => {
|
|
235
|
+
const open = tagAt(text, m.index), el = elementAt(text, m.index), closed = open.endsWith('/>') || el.endsWith(`</${m[0].slice(1).match(/^[\w.]+/)[0]}>`);
|
|
236
|
+
return { at: m.index, body: m.index + open.length, end: m.index + (closed ? el : open).length };
|
|
237
|
+
});
|
|
233
238
|
const stacks = span(/<SheetStack\b(?![\w.])/g), cards = span(/<Card\b(?![\w.])/g);
|
|
234
239
|
const within = (at, el) => at > el.at && at < el.end;
|
|
235
240
|
const innermost = (at) => cards.filter((c) => within(at, c)).at(-1);
|
|
241
|
+
// a form is never on the ground (Lh 2026-10-18, timebank's ask steps on a bare Page): the control's nearest container written in this file decides.
|
|
242
|
+
// None in the file (a step component's fragment) is not judged here — the file that places it is
|
|
243
|
+
const places = [
|
|
244
|
+
...span(/<(?:Sheet|SheetBody|SheetStep|StackSheet|SheetStack|CallSheet|Modal|ModalAsk)\b(?![\w.])/g).map((p) => ({ ...p, ground: false })), // a modal is a surface over the screen too
|
|
245
|
+
...span(/<(?:ActionBarTier|FilterRow|TopBar|AppBar)\b(?![\w.])/g).map((p) => ({ ...p, ground: false })), // a bar's control: a Slider in a control tier, a Dropdown in a filter row or a top bar's actions, a Toggle in an app bar
|
|
246
|
+
...span(/<(?:Page|Pages|Step|Screen)\b(?![\w.])/g).map((p) => ({ ...p, ground: true })),
|
|
247
|
+
...cards.map((p) => ({ ...p, ground: false })), // a card round it is field-in-card's finding
|
|
248
|
+
].sort((a, b) => a.at - b.at);
|
|
249
|
+
// true on a sheet, false on the ground, null when this file cannot tell. A component of this file is where it is placed: the ask steps' <Page>,
|
|
250
|
+
// in a function rendered inside a <SheetBody> further up the file, slides inside that sheet
|
|
251
|
+
const componentAt = (at) => [...text.slice(0, at).matchAll(/(?:function\s+|const\s+)([A-Z]\w*)/g)].at(-1)?.[1];
|
|
252
|
+
const onSheet = (at, depth = 0) => {
|
|
253
|
+
const place = places.filter((p) => within(at, p)).at(-1);
|
|
254
|
+
if (place && !place.ground) return true;
|
|
255
|
+
const name = depth < 5 && componentAt(at);
|
|
256
|
+
if (name) for (const u of text.matchAll(new RegExp(`(?<![\\w$)\\].])<${name}\\b(?![\\w.])`, 'g'))) if (onSheet(u.index, depth + 1)) return true;
|
|
257
|
+
return place ? false : null;
|
|
258
|
+
};
|
|
236
259
|
for (const c of text.matchAll(/<(Field|Composer|ComposerArea|Pin|Dropdown|Slider|Toggle|Check|ChipPick|Calendar|DayStrip|Day|Picker|Mood|AvatarPick|FileBtn|AddRow|MapPick|Tile)\b(?![\w.])/g)) {
|
|
237
260
|
const kind = c[1] === 'Tile' ? tagAt(text, c.index).match(/\s(pick|upload)\b(?!\s*=\s*\{\s*false)/)?.[1] : null;
|
|
238
261
|
if (c[1] === 'Tile' && !kind) continue; // a plain or stat Tile is read, not filled
|
|
239
262
|
const card = innermost(c.index);
|
|
240
|
-
if (!card)
|
|
263
|
+
if (!card) {
|
|
264
|
+
if (onSheet(c.index) === false) add('field-off-sheet', 'error', c.index, `<${kind ? `Tile ${kind}` : c[1]}> on the ground: a form is never on the ground, it sits only on a sheet (Lh 2026-10-18). A Page, Pages, Step or Screen is the base, not a sheet. Put the control on a Sheet's <SheetBody>, in a <SheetStep>, or on a stack sheet — a form over a screen is a <Sheet> in a <SheetStage over dim> (Home/AskSheet).`);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
241
267
|
const stack = stacks.filter((s) => card.at >= s.body && card.at < s.end).at(-1);
|
|
242
268
|
if (stack && !cards.some((o) => o !== card && within(card.at, o) && o.at >= stack.body)) continue;
|
|
243
269
|
const name = kind ? `Tile ${kind}` : c[1];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usableapp/cardds",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.100",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "card-first mobile design system, React-first: the components in src/ are thin wrappers over the CSS contract (css/*.css stays the only truth); gallery/ shows every story live (npm run dev), tests/ measures the geometry.",
|