@astryxdesign/cli 0.7.12-canary.f36bd72 → 0.7.12
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 +107 -0
- package/api/build/blocks/blocks.d.mts +41 -0
- package/api/build/blocks/blocks.mjs +118 -68
- package/api/doctor/doctor.d.mts +4 -4
- package/api/doctor/doctor.mjs +248 -78
- package/api/icon/_authoring.mjs +8 -1
- package/api/icon/add/add.mjs +45 -5
- package/api/icon/icon.type.d.mts +24 -0
- package/api/icon/icon.type.mjs +6 -0
- package/api/icon/iconAdd.doc.mjs +1 -1
- package/api/icon/map/map.mjs +11 -4
- package/api/integration/add-helpers.d.mts +5 -1
- package/api/integration/add-helpers.mjs +1 -1
- package/api/render/render.doc.mjs +1 -1
- package/api/render/render.type.d.mts +1 -1
- package/api/render/render.type.mjs +1 -1
- package/api/search/ORDERING.md +3 -0
- package/api/search/_adapter.d.mts +10 -2
- package/api/search/_adapter.mjs +13 -2
- package/api/search/_fit-order.d.mts +47 -0
- package/api/search/_fit-order.mjs +257 -0
- package/api/search/_main-searcher.mjs +15 -5
- package/api/template/skeleton/skeleton.mjs +1 -1
- package/assets/codemods/transforms/v0.1.0/drop-xds-prefix-imports.mjs +1 -1
- package/assets/codemods/transforms/v0.1.0/index.mjs +1 -1
- package/assets/docs/tree/icon-sets.doc.mjs +4 -4
- package/assets/docs/tree/render-pages.doc.mjs +1 -1
- package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.doc.mjs +2 -2
- package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.tsx +13 -7
- package/assets/templates/pages/table-grouped/page.tsx +30 -30
- package/assets/templates/themes/gothic/gothicTheme.ts +16 -0
- package/assets/templates/themes/matcha/matchaTheme.ts +1 -1
- package/assets/templates/themes/neutral/neutralTheme.ts +6 -0
- package/assets/templates/themes/y2k/y2kPaletteRefs.generated.ts +1 -1
- package/assets/templates/themes/y2k/y2kTheme.ts +1 -1
- package/clients/cli/commands/experimental/icon.mjs +25 -4
- package/clients/cli/commands/experimental-render.doc.mjs +1 -1
- package/clients/cli/commands/template.mjs +204 -181
- package/foundation/discovery/component-discovery.mjs +1 -1
- package/foundation/discovery/icon-set-writer.d.mts +21 -5
- package/foundation/discovery/icon-set-writer.mjs +114 -13
- package/foundation/discovery/template-adapter.mjs +2 -2
- package/foundation/render/page-host.d.mts +4 -0
- package/foundation/render/page-host.mjs +45 -0
- package/foundation/render/page-worker.mjs +11 -5
- package/foundation/render/render-page.d.mts +11 -6
- package/foundation/render/render-page.mjs +138 -44
- package/foundation/response/error-codes.doc.mjs +1 -1
- package/foundation/xle/registry-core.mjs +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file A last pass over a searcher's answer: inside each run of one score
|
|
5
|
+
* band and kind, the entries from #2 to #10 that fit the whole query much
|
|
6
|
+
* worse than the others in their run move, in order, after them.
|
|
7
|
+
* @input the ordered response, the gathered candidates, and the query
|
|
8
|
+
* @output the same response with the same results, maybe in a new order
|
|
9
|
+
* @position api/search; rankWith (_main-searcher.mjs) runs it after
|
|
10
|
+
* orderMatches, with the weights the adapter loads (loadSearchData) through
|
|
11
|
+
* build's own loader. The fit is build's block-picker match
|
|
12
|
+
* (build/blocks/blocks.mjs) with its shipped weights, so an entry the weights
|
|
13
|
+
* never saw is scored from its own docs the same way. Without readable
|
|
14
|
+
* weights the order stays.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
docSideOf,
|
|
19
|
+
matchOf,
|
|
20
|
+
requestSideOf,
|
|
21
|
+
stemOf,
|
|
22
|
+
termsOf,
|
|
23
|
+
wordsOf,
|
|
24
|
+
} from '../build/blocks/blocks.mjs';
|
|
25
|
+
import {bandOf} from './_order.mjs';
|
|
26
|
+
import {searchedComponentsOf} from './_result.mjs';
|
|
27
|
+
|
|
28
|
+
/** An entry stays in place when its fit is at least this times the median. */
|
|
29
|
+
export const FIT_BAR = 1.7;
|
|
30
|
+
|
|
31
|
+
/** An entry whose name spells at least this many query words never moves. */
|
|
32
|
+
const NAME_WORDS = 2;
|
|
33
|
+
|
|
34
|
+
/** The first result stays; the pass orders the places after it up to here. */
|
|
35
|
+
export const FIT_PLACES = 10;
|
|
36
|
+
|
|
37
|
+
/** @typedef {import('../build/blocks/blocks.mjs').BlockPickerWeights} Weights */
|
|
38
|
+
/** @typedef {{vector: Map<number, number>, projected: Float64Array}} Side */
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Each candidate by `domain:name`; a doc also by its topic and by
|
|
42
|
+
* `topic#section`. The first candidate with a key owns it.
|
|
43
|
+
* @type {WeakMap<any[], Map<string, any>>}
|
|
44
|
+
*/
|
|
45
|
+
const keyed = new WeakMap();
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Each candidate's docs side, computed when a result first needs it.
|
|
49
|
+
* @type {WeakMap<Weights, WeakMap<any, Side>>}
|
|
50
|
+
*/
|
|
51
|
+
const sides = new WeakMap();
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* An entry's docs text: its name words, display name or first title,
|
|
55
|
+
* description, and keywords.
|
|
56
|
+
* @param {any} c
|
|
57
|
+
*/
|
|
58
|
+
const docText = c =>
|
|
59
|
+
[
|
|
60
|
+
wordsOf(c.name).join(' '),
|
|
61
|
+
c._displayName ?? (Array.isArray(c.titles) ? c.titles[0] : '') ?? '',
|
|
62
|
+
c.description ?? '',
|
|
63
|
+
(Array.isArray(c.keywords) ? c.keywords : []).join(' '),
|
|
64
|
+
].join(' ');
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {any[]} candidates
|
|
68
|
+
* @returns {Map<string, any>}
|
|
69
|
+
*/
|
|
70
|
+
function keysOf(candidates) {
|
|
71
|
+
let byKey = keyed.get(candidates);
|
|
72
|
+
if (!byKey) {
|
|
73
|
+
byKey = new Map();
|
|
74
|
+
for (const c of candidates) {
|
|
75
|
+
const keys = [`${c.domain}:${c._resultName ?? c.name}`];
|
|
76
|
+
if (c.domain === 'doc' && c._topic)
|
|
77
|
+
keys.push(
|
|
78
|
+
c._section ? `doc:${c._topic}#${c._section}` : `doc:${c._topic}`,
|
|
79
|
+
);
|
|
80
|
+
for (const k of keys) if (!byKey.has(k)) byKey.set(k, c);
|
|
81
|
+
}
|
|
82
|
+
keyed.set(candidates, byKey);
|
|
83
|
+
}
|
|
84
|
+
return byKey;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {any} c
|
|
89
|
+
* @param {Weights} weights
|
|
90
|
+
* @returns {Side}
|
|
91
|
+
*/
|
|
92
|
+
function candidateSide(c, weights) {
|
|
93
|
+
let byCandidate = sides.get(weights);
|
|
94
|
+
if (!byCandidate) {
|
|
95
|
+
byCandidate = new WeakMap();
|
|
96
|
+
sides.set(weights, byCandidate);
|
|
97
|
+
}
|
|
98
|
+
let side = byCandidate.get(c);
|
|
99
|
+
if (!side) {
|
|
100
|
+
side = docSideOf(docText(c), weights);
|
|
101
|
+
byCandidate.set(c, side);
|
|
102
|
+
}
|
|
103
|
+
return side;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A result's docs side: its candidate's, else one from the result's own name,
|
|
108
|
+
* display name and description.
|
|
109
|
+
* @param {any} r
|
|
110
|
+
* @param {any[]} candidates
|
|
111
|
+
* @param {Weights} weights
|
|
112
|
+
* @returns {Side}
|
|
113
|
+
*/
|
|
114
|
+
function resultSideOf(r, candidates, weights) {
|
|
115
|
+
const byKey = keysOf(candidates);
|
|
116
|
+
if (r.domain === 'doc' && r.section) {
|
|
117
|
+
const c = byKey.get(`doc:${r.name}#${r.section}`);
|
|
118
|
+
if (c) return candidateSide(c, weights);
|
|
119
|
+
}
|
|
120
|
+
const c = byKey.get(`${r.domain}:${r.name}`);
|
|
121
|
+
return c
|
|
122
|
+
? candidateSide(c, weights)
|
|
123
|
+
: docSideOf(
|
|
124
|
+
`${wordsOf(r.name).join(' ')} ${r.displayName ?? ''} ${r.description ?? ''}`,
|
|
125
|
+
weights,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* A result's run: its score band and kind (doc or not), as orderMatches reads
|
|
131
|
+
* them.
|
|
132
|
+
* @param {any} r
|
|
133
|
+
*/
|
|
134
|
+
const runOf = r => `${bandOf(r.score ?? 0)}:${r.domain === 'doc'}`;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The results with the first kept. Entries #2 to #10 split into runs of one
|
|
138
|
+
* band and kind. Inside a run, the entries that fit the query under FIT_BAR
|
|
139
|
+
* times the run's median fit move, in order, after the others, unless their
|
|
140
|
+
* name spells NAME_WORDS or more of the query's words. A run whose
|
|
141
|
+
* median fit is zero or less keeps its order. Nothing crosses a run, and
|
|
142
|
+
* nothing is added or dropped.
|
|
143
|
+
* @param {any[]} results
|
|
144
|
+
* @param {any[]} candidates
|
|
145
|
+
* @param {string} query
|
|
146
|
+
* @param {Weights | (() => Weights | null | undefined) | null | undefined} weights
|
|
147
|
+
* the block picker's weights, or a function that reads them; the function
|
|
148
|
+
* runs only when a run can change. Without weights the order stays
|
|
149
|
+
* @returns {any[]}
|
|
150
|
+
*/
|
|
151
|
+
export function orderByFit(results, candidates, query, weights) {
|
|
152
|
+
if (!weights || results.length < 3) return results;
|
|
153
|
+
const end = Math.min(FIT_PLACES, results.length);
|
|
154
|
+
/** @type {[number, number][]} */
|
|
155
|
+
const runs = [];
|
|
156
|
+
for (let i = 1; i < end;) {
|
|
157
|
+
let j = i;
|
|
158
|
+
while (j < end && runOf(results[j]) === runOf(results[i])) j++;
|
|
159
|
+
runs.push([i, j]);
|
|
160
|
+
i = j;
|
|
161
|
+
}
|
|
162
|
+
// Only a run of two or more can change, so the weights stay unread when
|
|
163
|
+
// there is none.
|
|
164
|
+
if (!runs.some(([i, j]) => j - i > 1)) return results;
|
|
165
|
+
const w = typeof weights === 'function' ? weights() : weights;
|
|
166
|
+
if (!w) return results;
|
|
167
|
+
const request = requestSideOf(query, w);
|
|
168
|
+
const queryWords = new Set(termsOf(query).filter(t => !t.includes('_')));
|
|
169
|
+
/** @param {any} r */
|
|
170
|
+
const named = r =>
|
|
171
|
+
new Set(
|
|
172
|
+
wordsOf(r.name)
|
|
173
|
+
.map(stemOf)
|
|
174
|
+
.filter(word => queryWords.has(word)),
|
|
175
|
+
).size >= NAME_WORDS;
|
|
176
|
+
const out = results.slice();
|
|
177
|
+
let changed = false;
|
|
178
|
+
for (const [i, j] of runs) {
|
|
179
|
+
if (j - i < 2) continue;
|
|
180
|
+
const moved = demoteWeak(
|
|
181
|
+
results.slice(i, j),
|
|
182
|
+
request,
|
|
183
|
+
candidates,
|
|
184
|
+
w,
|
|
185
|
+
named,
|
|
186
|
+
);
|
|
187
|
+
moved.forEach((r, k) => {
|
|
188
|
+
if (r !== out[i + k]) changed = true;
|
|
189
|
+
out[i + k] = r;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return changed ? out : results;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* One run, with the entries under FIT_BAR times its median fit moved, in
|
|
197
|
+
* order, after the others. An entry whose name spells NAME_WORDS or more of
|
|
198
|
+
* the query's words stays with the others: its name is strong evidence.
|
|
199
|
+
* @param {any[]} run
|
|
200
|
+
* @param {Side} request
|
|
201
|
+
* @param {any[]} candidates
|
|
202
|
+
* @param {Weights} weights
|
|
203
|
+
* @param {(r: any) => boolean} named
|
|
204
|
+
* @returns {any[]}
|
|
205
|
+
*/
|
|
206
|
+
function demoteWeak(run, request, candidates, weights, named) {
|
|
207
|
+
const fits = run.map(
|
|
208
|
+
r => matchOf(request, resultSideOf(r, candidates, weights), weights).match,
|
|
209
|
+
);
|
|
210
|
+
const sorted = [...fits].sort((a, b) => a - b);
|
|
211
|
+
const n = sorted.length;
|
|
212
|
+
const median =
|
|
213
|
+
n % 2 ? sorted[(n - 1) / 2] : (sorted[n / 2 - 1] + sorted[n / 2]) / 2;
|
|
214
|
+
if (!(median > 0)) return run;
|
|
215
|
+
const bar = FIT_BAR * median;
|
|
216
|
+
const kept = run.map((r, k) => fits[k] >= bar || named(r));
|
|
217
|
+
return [...run.filter((_, k) => kept[k]), ...run.filter((_, k) => !kept[k])];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* The search response with its results ordered by fit (`orderByFit`). Any
|
|
222
|
+
* other response passes through.
|
|
223
|
+
* @template T
|
|
224
|
+
* @param {T} response
|
|
225
|
+
* @param {any[]} candidates
|
|
226
|
+
* @param {string} query
|
|
227
|
+
* @param {Weights | (() => Weights | null | undefined) | null | undefined} weights
|
|
228
|
+
* @returns {T}
|
|
229
|
+
*/
|
|
230
|
+
export function withFitOrder(response, candidates, query, weights) {
|
|
231
|
+
const r = /** @type {any} */ (response);
|
|
232
|
+
const results = r?.data?.results;
|
|
233
|
+
if (!Array.isArray(results)) return response;
|
|
234
|
+
const ordered = orderByFit(results, candidates, query, weights);
|
|
235
|
+
if (ordered === results) return response;
|
|
236
|
+
const out = {...r, data: {...r.data, results: ordered}};
|
|
237
|
+
const searched = searchedComponentsOf.get(r);
|
|
238
|
+
if (searched) searchedComponentsOf.set(out, searched);
|
|
239
|
+
return /** @type {T} */ (out);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The search response with at most `limit` results.
|
|
244
|
+
* @template T
|
|
245
|
+
* @param {T} response
|
|
246
|
+
* @param {number} limit
|
|
247
|
+
* @returns {T}
|
|
248
|
+
*/
|
|
249
|
+
export function withLimit(response, limit) {
|
|
250
|
+
const r = /** @type {any} */ (response);
|
|
251
|
+
const results = r?.data?.results;
|
|
252
|
+
if (!Array.isArray(results) || results.length <= limit) return response;
|
|
253
|
+
const out = {...r, data: {...r.data, results: results.slice(0, limit)}};
|
|
254
|
+
const searched = searchedComponentsOf.get(r);
|
|
255
|
+
if (searched) searchedComponentsOf.set(out, searched);
|
|
256
|
+
return /** @type {T} */ (out);
|
|
257
|
+
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import {routeKit} from '../../foundation/route/route.mjs';
|
|
16
16
|
import {rank, signals} from './_features.mjs';
|
|
17
17
|
import {entryTable} from './_entries.mjs';
|
|
18
|
+
import {FIT_PLACES, withFitOrder, withLimit} from './_fit-order.mjs';
|
|
18
19
|
import {orderMatches} from './_order.mjs';
|
|
19
20
|
import {routeProbs} from './_query-words.mjs';
|
|
20
21
|
import {namesWhole, rankLexical} from './_lexical.mjs';
|
|
@@ -94,13 +95,22 @@ export function rankWith(request, catalog, weights) {
|
|
|
94
95
|
);
|
|
95
96
|
const route = routeProbs(catalog.data.router, query);
|
|
96
97
|
const rows = signals(table, query, lexical.data.results, route);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
const limit = options.limit ?? 20;
|
|
99
|
+
// The fit pass reads the first FIT_PLACES results at every limit, so a
|
|
100
|
+
// smaller limit shows the top of that order.
|
|
101
|
+
const ordered = withFitOrder(
|
|
102
|
+
orderMatches(
|
|
103
|
+
table,
|
|
104
|
+
rank(table, rows, weights),
|
|
105
|
+
lexical,
|
|
106
|
+
query,
|
|
107
|
+
Math.max(limit, FIT_PLACES),
|
|
108
|
+
),
|
|
109
|
+
catalog.all,
|
|
101
110
|
query,
|
|
102
|
-
|
|
111
|
+
() => catalog.data.picker,
|
|
103
112
|
);
|
|
113
|
+
return withLimit(ordered, limit);
|
|
104
114
|
}
|
|
105
115
|
|
|
106
116
|
/** @type {import('./tree.mjs').SearchKit} */
|
|
@@ -146,7 +146,7 @@ function extractSkeleton(source) {
|
|
|
146
146
|
|
|
147
147
|
// Match a JSX opening tag, e.g. `<Section` or the legacy `<XDSSection`.
|
|
148
148
|
// The `XDS` prefix is optional (templates author bare names post
|
|
149
|
-
// un-prefix migration
|
|
149
|
+
// un-prefix migration). `tagName` is the full authored name
|
|
150
150
|
// (used for the self-closing lookahead); `comp` is the bare display name.
|
|
151
151
|
const openMatch = t.match(/^<((XDS)?([A-Z]\w+))/);
|
|
152
152
|
if (openMatch && !t.startsWith('</')) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file Codemod: Drop the `XDS` prefix from @xds/core imports and their usages
|
|
5
5
|
*
|
|
6
|
-
* Part of the XDS-prefix migration
|
|
6
|
+
* Part of the XDS-prefix migration. Rewrites consumer code from
|
|
7
7
|
* the prefixed names to their bare equivalents:
|
|
8
8
|
*
|
|
9
9
|
* XDSButton -> Button
|
|
@@ -24,7 +24,7 @@ import migrateXdsCssSurfaces, {
|
|
|
24
24
|
|
|
25
25
|
export default [
|
|
26
26
|
{
|
|
27
|
-
// XDS-prefix migration
|
|
27
|
+
// XDS-prefix migration. Mandatory in v0.1.0: the release
|
|
28
28
|
// dropped the prefixed compatibility aliases (useXDSTheme, XDSButton,
|
|
29
29
|
// XDSIconRegistry, ...), so consumers upgrading from 0.0.x MUST rewrite
|
|
30
30
|
// prefixed imports to their bare names.
|
|
@@ -61,7 +61,7 @@ export const docs = {
|
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
63
|
type: 'prose',
|
|
64
|
-
text: "`outline.doc.mjs` is the descriptor: name, display name and description (`--display-name`, `--description`), and `license`, the SPDX license of the artwork (it starts as your package's own). `outline.icons.json` starts with no icons and an empty `map`; its `module` is your package name. `outline.generated.js` is the set's generated module, with its declaration file `outline.generated.d.ts`: the CLI writes both, and every icon and the theme map are exports of it. Every field is in {@link schema:icon-set-doc} and {@link schema:icon-set-data}.",
|
|
64
|
+
text: "`outline.doc.mjs` is the descriptor: name, display name and description (`--display-name`, `--description`), and `license`, the SPDX license of the artwork (it starts as your package's own). `outline.icons.json` starts with no icons and an empty `map`; its `module` is your package name. After you rename the package, the next command that writes the data file (`new-set` on the existing set, with or without `--from`, `add`, `map`, or `new-definition --icon`) writes the new name and keeps any subpath (`@acme/old-icons/outline` becomes `@acme/new-icons/outline`), and its receipt says so. `outline.generated.js` is the set's generated module, with its declaration file `outline.generated.d.ts`: the CLI writes both, and every icon and the theme map are exports of it. Every field is in {@link schema:icon-set-doc} and {@link schema:icon-set-data}.",
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
type: 'prose',
|
|
@@ -121,7 +121,7 @@ export const docs = {
|
|
|
121
121
|
},
|
|
122
122
|
{
|
|
123
123
|
type: 'prose',
|
|
124
|
-
text: "Every component the library exports becomes an icon (an alias of one component is recorded once), named `<Name>Icon`, with an id from its name (`Squares2X2Icon` is `squares-2x2`: a dimension stays one word). `map` is filled only where a definition's name is an icon's id (`delete` → icon `delete`), and the receipt lists those entries under Mapped by name: a library can draw another meaning under the same name (one library's `delete` is a backspace key), so check each. An icon whose id is only one of a definition's words (`trash` for `delete`) may mean something else in this library, so the receipt lists it under Suggestions, with the `experimental icon map <definition> <icon>` command for each, and writes nothing for it: check the meaning, then run the ones that fit. Run it again after a library release to refresh the icons that library supplies; `map` entries whose icon still exists are kept, and the receipt names any icon the library dropped, any icon `--exclude` left out, and the `map` entries each took with it. Icons you added from your own files or another library are left as they are. Icons you added one file at a time from the same package (`--from <package>/dist/XIcon`) move to the root import when you refresh from the package root and the root exports the same icon; each keeps its id, export, `map` entries, variants, and words on that refresh and every later one, and the receipt lists it under `moved`. A library icon whose id or name a moved icon already has is left out. When another icon of the set already reads the same root export, the icon stays on its own file, and the receipt says why. An icon you already have is known by the library export it re-exports, so it keeps its export name exactly, its `map` entries, variants, and words, even when a newer CLI spells its id differently or your `--exclude` lets a library prefix come off new ids; the receipt lists each changed id under `renamed`. An id you chose that is not a spelling of the library name (`dismiss` for `XIcon`) is kept. A refresh with `--dry-run` writes nothing and prints the Mapped by name and Suggestions lists again; `--display-name` and `--description` on a refresh rewrite the descriptor. Variants added with `add --variant … --all` are kept too; run that `add` again to pick up icons the release added. `experimental icon --list` shows how many definitions the set maps.",
|
|
124
|
+
text: "Every component the library exports becomes an icon (an alias of one component is recorded once), named `<Name>Icon`, with an id from its name (`Squares2X2Icon` is `squares-2x2`: a dimension stays one word). An export name cannot start with a digit, so an icon whose id would start with one once the library prefix comes off keeps the prefix (`fa0` is id `fa-0`, exported as `Fa0Icon`), and an id you give with `--id` must start with a letter. `map` is filled only where a definition's name is an icon's id (`delete` → icon `delete`), and the receipt lists those entries under Mapped by name: a library can draw another meaning under the same name (one library's `delete` is a backspace key), so check each. An icon whose id is only one of a definition's words (`trash` for `delete`) may mean something else in this library, so the receipt lists it under Suggestions, with the `experimental icon map <definition> <icon>` command for each, and writes nothing for it: check the meaning, then run the ones that fit. Run it again after a library release to refresh the icons that library supplies; `map` entries whose icon still exists are kept, and the receipt names any icon the library dropped, any icon `--exclude` left out, and the `map` entries each took with it. Icons you added from your own files or another library are left as they are. Icons you added one file at a time from the same package (`--from <package>/dist/XIcon`) move to the root import when you refresh from the package root and the root exports the same icon; each keeps its id, export, `map` entries, variants, and words on that refresh and every later one, and the receipt lists it under `moved`. A library icon whose id or name a moved icon already has is left out. When another icon of the set already reads the same root export, the icon stays on its own file, and the receipt says why. An icon you already have is known by the library export it re-exports, so it keeps its export name exactly, its `map` entries, variants, and words, even when a newer CLI spells its id differently or your `--exclude` lets a library prefix come off new ids; the receipt lists each changed id under `renamed`. An id you chose that is not a spelling of the library name (`dismiss` for `XIcon`) is kept. A refresh with `--dry-run` writes nothing and prints the Mapped by name and Suggestions lists again; `--display-name` and `--description` on a refresh rewrite the descriptor. Variants added with `add --variant … --all` are kept too; run that `add` again to pick up icons the release added. `experimental icon --list` shows how many definitions the set maps.",
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
type: 'prose',
|
|
@@ -139,11 +139,11 @@ astryx experimental icon add --variant solid --from @heroicons/react/24/solid --
|
|
|
139
139
|
},
|
|
140
140
|
{
|
|
141
141
|
type: 'prose',
|
|
142
|
-
text: "A library that redraws its artwork for small sizes (Heroicons' 20px mini and 16px micro) ships each of those as its own set in its own package (their export names match the 24px ones), not as variants: a variant's size comes from CSS. `--variants` lists the variants; `--default-variant` names the one each icon's own export draws (when omitted, the look a library that names its looks draws; otherwise the set's current default, else, on a set without icons, the first variant; a set with icons changes it only with `--from`, and only in a library that names its looks). `--all` adds the variant for every icon whose id an export of `--from` has (`TrashIcon` in the solid module becomes `TrashSolidIcon`, the solid variant of `trash`), and the receipt lists the icons the module has no export for. Running it again changes nothing.",
|
|
142
|
+
text: "A library that redraws its artwork for small sizes (Heroicons' 20px mini and 16px micro) ships each of those as its own set in its own package (their export names match the 24px ones), not as variants: a variant's size comes from CSS. `--variants` lists the variants; `--default-variant` names the one each icon's own export draws (when omitted, the look a library that names its looks draws; otherwise the set's current default, else, on a set without icons, the first variant; a set with icons changes it only with `--from`, and only in a library that names its looks). `--all` adds the variant for every icon whose id an export of `--from` has (`TrashIcon` in the solid module becomes `TrashSolidIcon`, the solid variant of `trash`), and the receipt lists the icons the module has no export for. A library may ship each look as its own package (Font Awesome's solid and regular packages): `--from` may then name the other package, and an icon gains the variant from the export of the same name there; the receipt lists the icons that package has no export for. Running it again changes nothing.",
|
|
143
143
|
},
|
|
144
144
|
{
|
|
145
145
|
type: 'prose',
|
|
146
|
-
text: 'A library whose icons are data (objects or SVG strings) has no components to find: create the set, declare an `adapter` in its data file (see library shapes), and run the command again; every data export then becomes an icon through the adapter. When every icon export shares a leading word, such as `feather` in `featherEye` and `featherSun`, ids and export names leave it out: `featherEye` becomes id `eye`, exported as `EyeIcon`. The prefix is read over icon-like exports only: SCREAMING_CASE constants (`README`, `VERSION`), number and boolean exports, and, next to components or icon objects, plain strings are never icons. Components named `…Provider` or `…Context` are never icons either
|
|
146
|
+
text: 'A library whose icons are data (objects or SVG strings) has no components to find: create the set, declare an `adapter` in its data file (see library shapes), and run the command again; every data export then becomes an icon through the adapter. When every icon export shares a leading word, such as `feather` in `featherEye` and `featherSun`, ids and export names leave it out: `featherEye` becomes id `eye`, exported as `EyeIcon`. The prefix is read over icon-like exports only: SCREAMING_CASE constants (`README`, `VERSION`), number and boolean exports, and, next to components or icon objects, plain strings are never icons. Components named `…Provider` or `…Context` are never icons either, and neither is a pack: an object whose keys are all other exports of the module, such as `fas` next to `faXmark`. Leave out any other export that is not an icon with `--exclude meta,version`: the data file keeps the list in `exclude`, so a refresh leaves them out too. When a library exports one icon under several names (`Trash2` and `Trash`), the icon is recorded once and its other names become words, so `experimental icon trash-2` finds it and `experimental icon map delete trash-2` maps it. `experimental icon add` reads the prefix the same way, and reuses the icon an export already has, so its commands need no `--id`.',
|
|
147
147
|
},
|
|
148
148
|
],
|
|
149
149
|
},
|
|
@@ -67,7 +67,7 @@ export const docs = {
|
|
|
67
67
|
content: [
|
|
68
68
|
{
|
|
69
69
|
type: 'prose',
|
|
70
|
-
text: 'Each warning names something that may look different from what you meant, and what to change. The most common is a component that ignores its children: `<ListItem>Save</ListItem>` renders an empty row because ListItem takes `label="Save"`. Others flag remote images (a sandboxed viewer cannot load them; save the image next to the page and import it), a `<script>`, `<link>` or `<iframe>` the page itself renders (the file promises to run and load nothing, so remove them), missing images, and output the page printed while rendering.',
|
|
70
|
+
text: 'Each warning names something that may look different from what you meant, and what to change. The most common is a component that ignores its children: `<ListItem>Save</ListItem>` renders an empty row because ListItem takes `label="Save"`. Others flag remote images (a sandboxed viewer cannot load them; save the image next to the page and import it), a `<script>`, `<link>` or `<iframe>` the page itself renders (the file promises to run and load nothing, so remove them), missing images, and output the page printed while rendering. A `node-crash` warning names a Node build that crashes in `Intl.Segmenter` (its version and path). The page rendered without it, so the output may differ slightly: initials and character counts (such as Avatar initials) can split a joined emoji, a flag, or a letter written with a separate accent mark. Names in ordinary composed letters (`José`, `Zoë`, `李小龙`) come out the same. Render with an official Node.js build to remove the difference.',
|
|
71
71
|
},
|
|
72
72
|
],
|
|
73
73
|
},
|
|
@@ -7,8 +7,8 @@ export const doc = {
|
|
|
7
7
|
name: 'useTableRowsVirtualization - Fixed Height Rows',
|
|
8
8
|
displayName: 'useTableRowsVirtualization - Fixed Height Rows',
|
|
9
9
|
description:
|
|
10
|
-
|
|
10
|
+
"A Table with 1,000 product rows inside a 320px-tall parent, which bounds its scroll area. Give the Table's parent a height, fixed or relative, or a flex child, so windowing can bound the rows; with no height every row renders. Scroll vertically to mount the visible rows. The caller keeps all data and columns; this hook does not virtualize columns.",
|
|
11
11
|
isReady: true,
|
|
12
12
|
aspectRatio: 16 / 9,
|
|
13
|
-
componentsUsed: ['Table'],
|
|
13
|
+
componentsUsed: ['Table', 'VStack'],
|
|
14
14
|
};
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
useTableRowsVirtualization,
|
|
8
8
|
type TableColumn,
|
|
9
9
|
} from '@astryxdesign/core/Table';
|
|
10
|
+
import {VStack} from '@astryxdesign/core/Layout';
|
|
10
11
|
|
|
11
12
|
interface Product extends Record<string, unknown> {
|
|
12
13
|
id: string;
|
|
@@ -22,14 +23,19 @@ const columns: TableColumn<Product>[] = [{key: 'name', header: 'Product'}];
|
|
|
22
23
|
export default function RowsVirtualizationHookUsage() {
|
|
23
24
|
const virtualization = useTableRowsVirtualization({
|
|
24
25
|
rowHeight: 56,
|
|
25
|
-
|
|
26
|
+
initialViewportHeight: 320,
|
|
26
27
|
});
|
|
28
|
+
// Give the Table's parent a height, fixed or relative, or a flex child, so
|
|
29
|
+
// windowing can bound the rows; with no height every row renders.
|
|
30
|
+
// initialViewportHeight only sizes the first window.
|
|
27
31
|
return (
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
<VStack height={320}>
|
|
33
|
+
<Table
|
|
34
|
+
data={products}
|
|
35
|
+
columns={columns}
|
|
36
|
+
idKey="id"
|
|
37
|
+
virtualization={virtualization}
|
|
38
|
+
/>
|
|
39
|
+
</VStack>
|
|
34
40
|
);
|
|
35
41
|
}
|