@es3d/es3d 1.0.1 → 1.0.2

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.
@@ -6,7 +6,8 @@
6
6
  // detectPatterns sibling copies whose transforms form a progression -> <repeat>
7
7
  // collapseGroups drop empty groups and attribute-less single-child groups
8
8
  // weldMeshes merge duplicate vertices of <mesh> elements
9
- // removeUnused drop definitions and materials nothing references
9
+ // removeUnused drop definitions and materials nothing references,
10
+ // counting ids named by an x- attribute
10
11
  // precision quantize numbers when serializing (lossy, opt-in)
11
12
  //
12
13
  // plus expandProcedural(), the inverse operation used by the benchmark: it
@@ -163,21 +164,36 @@ function stripDefaults(root, report) {
163
164
  if (n) report.push({ pass: 'stripDefaults', detail: `removed ${n} attribute${n === 1 ? '' : 's'} equal to the default` });
164
165
  }
165
166
 
167
+ /** An id token as written in an attribute: an optional leading # is not part of the id. */
168
+ function idToken(token) {
169
+ return token.replace(/^#/, '');
170
+ }
171
+
172
+ /** Rewrite id tokens inside an x- attribute, keeping the original separators and any leading #. */
173
+ function rewriteExtensionTokens(value, mapping) {
174
+ return value.replace(/[^\s,]+/g, (token) => {
175
+ const id = idToken(token);
176
+ if (!mapping.has(id)) return token;
177
+ return (token.startsWith('#') ? '#' : '') + mapping.get(id);
178
+ });
179
+ }
180
+
166
181
  function rewriteRefs(root, mapping, kinds) {
167
182
  walkAll(root, (el) => {
168
183
  for (const a of el.attrs) {
169
184
  const k = canonicalAttr(el, a);
170
185
  const s = attrSpec(el.name, k);
171
- if (!s) continue;
172
- if (s.type === 'ref' && kinds.includes(s.ref)) {
173
- const id = a.value.trim().replace(/^#/, '');
186
+ if (s && s.type === 'ref' && kinds.includes(s.ref)) {
187
+ const id = idToken(a.value.trim());
174
188
  if (mapping.has(id)) a.value = mapping.get(id);
175
- } else if (s.type === 'idlist' && kinds.includes(s.ref)) {
189
+ } else if (s && s.type === 'idlist' && kinds.includes(s.ref)) {
176
190
  a.value = a.value
177
191
  .split(/[\s,]+/)
178
192
  .filter(Boolean)
179
- .map((id) => mapping.get(id.replace(/^#/, '')) || id)
193
+ .map((id) => mapping.get(idToken(id)) || id)
180
194
  .join(',');
195
+ } else if (!s && el.name.startsWith('x-')) {
196
+ a.value = rewriteExtensionTokens(a.value, mapping);
181
197
  }
182
198
  }
183
199
  });
@@ -211,12 +227,21 @@ function dedupeMaterials(root, report) {
211
227
 
212
228
  function collectReferences(root) {
213
229
  const used = new Set();
230
+ // x- attributes are not in the schema. A token counts only when it names an id
231
+ // that exists, so prose in an extension cannot pin an unrelated definition.
232
+ const ids = usedIds(root);
214
233
  walkAll(root, (el) => {
215
234
  for (const a of el.attrs) {
216
235
  const s = attrSpec(el.name, canonicalAttr(el, a));
217
- if (!s) continue;
218
- if (s.type === 'ref') used.add(a.value.trim().replace(/^#/, ''));
219
- else if (s.type === 'idlist') for (const id of a.value.split(/[\s,]+/)) if (id) used.add(id.replace(/^#/, ''));
236
+ if (s) {
237
+ if (s.type === 'ref') used.add(idToken(a.value.trim()));
238
+ else if (s.type === 'idlist') for (const id of a.value.split(/[\s,]+/)) if (id) used.add(idToken(id));
239
+ } else if (el.name.startsWith('x-')) {
240
+ for (const token of a.value.split(/[\s,]+/)) {
241
+ const id = idToken(token);
242
+ if (id && ids.has(id)) used.add(id);
243
+ }
244
+ }
220
245
  }
221
246
  });
222
247
  return used;
@@ -23,6 +23,7 @@
23
23
  // automatic orbit; scroll-driven cameras jump between stops.
24
24
 
25
25
  import { compile } from '../pipeline.js';
26
+ import { formatDiagnostics } from '../format/parse.js';
26
27
  import { updateDynamic } from '../scene/flatten.js';
27
28
  import { pick } from '../scene/picking.js';
28
29
  import { createRenderer, PROGRESSIVE_BUDGET_MS } from './renderer.js';
@@ -267,7 +268,7 @@ export async function createStage(canvas, opts = {}) {
267
268
  // and the renderer finds those meshes already on the GPU.
268
269
  const result = compile(source, { time: state.time, progress: state.progress, cache: state.result ? state.result.cache : undefined });
269
270
  if (!result.ok) {
270
- const msg = result.diagnostics.map((d) => d.format()).join('\n\n');
271
+ const msg = formatDiagnostics(result);
271
272
  if (opts.onError) opts.onError(msg, result);
272
273
  else console.error(msg);
273
274
  return result;