wgsl-edit 0.0.24 → 0.0.26

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/dist/wgsl-edit.js CHANGED
@@ -5073,11 +5073,11 @@ var shift = {
5073
5073
  };
5074
5074
  var mac = typeof navigator != "undefined" && /Mac/.test(navigator.platform);
5075
5075
  var ie$1 = typeof navigator != "undefined" && /MSIE \d|Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);
5076
- for (var i$1 = 0; i$1 < 10; i$1++) base[48 + i$1] = base[96 + i$1] = String(i$1);
5077
- for (var i$1 = 1; i$1 <= 24; i$1++) base[i$1 + 111] = "F" + i$1;
5078
- for (var i$1 = 65; i$1 <= 90; i$1++) {
5079
- base[i$1] = String.fromCharCode(i$1 + 32);
5080
- shift[i$1] = String.fromCharCode(i$1);
5076
+ for (var i = 0; i < 10; i++) base[48 + i] = base[96 + i] = String(i);
5077
+ for (var i = 1; i <= 24; i++) base[i + 111] = "F" + i;
5078
+ for (var i = 65; i <= 90; i++) {
5079
+ base[i] = String.fromCharCode(i + 32);
5080
+ shift[i] = String.fromCharCode(i);
5081
5081
  }
5082
5082
  for (var code in base) if (!shift.hasOwnProperty(code)) shift[code] = base[code];
5083
5083
  function keyName(event) {
@@ -15018,7 +15018,7 @@ tagHighlighter([
15018
15018
  ]);
15019
15019
  //#endregion
15020
15020
  //#region ../../node_modules/.pnpm/@codemirror+language@6.11.2/node_modules/@codemirror/language/dist/index.js
15021
- var _a$1;
15021
+ var _a;
15022
15022
  /**
15023
15023
  Node prop stored in a parser's top syntax node to provide the
15024
15024
  facet that stores language-specific data for that language.
@@ -15479,7 +15479,7 @@ if (typeof requestIdleCallback != "undefined") requestIdle = (callback) => {
15479
15479
  }, 100);
15480
15480
  return () => idle < 0 ? clearTimeout(timeout) : cancelIdleCallback(idle);
15481
15481
  };
15482
- const isInputPending = typeof navigator != "undefined" && ((_a$1 = navigator.scheduling) === null || _a$1 === void 0 ? void 0 : _a$1.isInputPending) ? () => navigator.scheduling.isInputPending() : null;
15482
+ const isInputPending = typeof navigator != "undefined" && ((_a = navigator.scheduling) === null || _a === void 0 ? void 0 : _a.isInputPending) ? () => navigator.scheduling.isInputPending() : null;
15483
15483
  const parseWorker = /* @__PURE__ */ ViewPlugin.fromClass(class ParseWorker {
15484
15484
  constructor(view) {
15485
15485
  this.view = view;
@@ -19035,12 +19035,13 @@ const isBrowser = "document" in globalThis;
19035
19035
  /** Throw an error with an embedded source map so that browser users can
19036
19036
  * click on the error in the browser debug console and see the wesl source code. */
19037
19037
  function throwClickableError(params) {
19038
- const { url, text, lineNumber, lineColumn, length, error } = params;
19038
+ const { url, text, lineNumber, lineColumn, length, offset, error } = params;
19039
19039
  const weslLocation = {
19040
19040
  file: url,
19041
19041
  line: lineNumber,
19042
19042
  column: lineColumn,
19043
- length
19043
+ length,
19044
+ offset
19044
19045
  };
19045
19046
  error.weslLocation = weslLocation;
19046
19047
  if (!isBrowser) throw error;
@@ -19101,6 +19102,7 @@ function failIdentElem(identElem, msg = "") {
19101
19102
  lineNumber,
19102
19103
  lineColumn,
19103
19104
  length: end - start,
19105
+ offset: start,
19104
19106
  error: new Error(detailedMessage)
19105
19107
  });
19106
19108
  }
@@ -19376,7 +19378,7 @@ function lowerAndEmitElem(e, ctx) {
19376
19378
  case "let":
19377
19379
  case "statement":
19378
19380
  case "continuing":
19379
- emitStatement$1(e, ctx);
19381
+ emitStatement(e, ctx);
19380
19382
  return;
19381
19383
  case "override":
19382
19384
  case "const":
@@ -19414,7 +19416,7 @@ function emitModule(e, ctx) {
19414
19416
  lowerAndEmitElem(child, ctx);
19415
19417
  }
19416
19418
  }
19417
- function emitStatement$1(e, ctx) {
19419
+ function emitStatement(e, ctx) {
19418
19420
  if (!(e.contents.length > 0 && e.contents[0].kind === "attribute")) emitAttributes(e.attributes, ctx);
19419
19421
  emitContents(e, ctx);
19420
19422
  }
@@ -22045,9 +22047,8 @@ function flatImports(ast, conditions) {
22045
22047
  //#region ../wesl/src/BindIdents.ts
22046
22048
  /** Bind ref idents to declarations and mangle global declaration names. */
22047
22049
  function bindIdents(params) {
22048
- const { rootAst, resolver, virtuals, accumulateUnbound } = params;
22050
+ const { rootAst, resolver, virtuals, accumulateUnbound, discoveryMode } = params;
22049
22051
  const { conditions = {}, mangler = minimalMangle } = params;
22050
- const { discoveryMode } = params;
22051
22052
  const packageName = rootAst.srcModule.modulePath.split("::")[0];
22052
22053
  const rootDecls = discoveryMode ? findAllRootDecls(rootAst.rootScope) : findValidRootDecls(rootAst.rootScope, conditions);
22053
22054
  const { globalNames, knownDecls } = initRootDecls(rootDecls);
@@ -22058,6 +22059,7 @@ function bindIdents(params) {
22058
22059
  virtuals,
22059
22060
  mangler,
22060
22061
  packageName,
22062
+ rootModulePath: rootAst.srcModule.modulePath,
22061
22063
  foundScopes: /* @__PURE__ */ new Set(),
22062
22064
  globalNames,
22063
22065
  globalStatements: /* @__PURE__ */ new Map(),
@@ -22068,8 +22070,9 @@ function bindIdents(params) {
22068
22070
  decls: new Map(rootDecls.map((d) => [d.originalName, d])),
22069
22071
  parent: null
22070
22072
  };
22071
- const fromRootDecls = rootDecls.flatMap((decl) => processDependentScope(decl, bindContext));
22072
- const fromRefs = bindIdentsRecursive(rootAst.rootScope, bindContext, liveDecls);
22073
+ const fromRootDecls = rootDecls.flatMap((d) => processDependentScope(d, bindContext));
22074
+ const { rootScope } = rootAst;
22075
+ const fromRefs = bindIdentsRecursive(rootScope, bindContext, liveDecls);
22073
22076
  const newStatements = [...bindContext.globalStatements.values()];
22074
22077
  return {
22075
22078
  decls: [...fromRootDecls, ...fromRefs],
@@ -22114,11 +22117,7 @@ function findAllRootDecls(rootScope) {
22114
22117
  function publicDecl(scope, name, conditions) {
22115
22118
  return getValidRootDecls(scope, conditions).find((d) => d.originalName === name);
22116
22119
  }
22117
- /**
22118
- * Recursively bind references to declarations in this scope and child scopes.
22119
- * Tracks @else state to ensure filtered @else blocks don't pull in unused declarations.
22120
- * @return new declarations found
22121
- */
22120
+ /** Recursively bind refs to decls in this scope and children. @return new declarations found */
22122
22121
  function bindIdentsRecursive(scope, bindContext, liveDecls) {
22123
22122
  const { dontFollowDecls, foundScopes } = bindContext;
22124
22123
  if (foundScopes.has(scope)) return [];
@@ -22156,11 +22155,7 @@ function processDependentScope(decl, ctx) {
22156
22155
  if (!rootDecls) return [];
22157
22156
  return bindIdentsRecursive(dependentScope, ctx, makeLiveDecls(rootDecls));
22158
22157
  }
22159
- /**
22160
- * Trace references to their declarations.
22161
- * Mutates to mangle declarations and mark std references.
22162
- * @return found declaration, or undefined if already processed
22163
- */
22158
+ /** Resolve a ref to its declaration, mangling globals and marking std refs. */
22164
22159
  function handleRef(ident, liveDecls, bindContext) {
22165
22160
  if (ident.refersTo || ident.std) return;
22166
22161
  if (ident.conditionRef) return;
@@ -22240,7 +22235,8 @@ function findExport(pathParts, srcModule, ctx) {
22240
22235
  const modulePath = resolveModulePath(pathParts, srcModule.modulePath.split("::")).slice(0, -1).join("::");
22241
22236
  const moduleAst = ctx.resolver.resolveModule(modulePath) ?? virtualModule(pathParts[0], ctx);
22242
22237
  if (!moduleAst) return void 0;
22243
- const decl = publicDecl(moduleAst.rootScope, last(pathParts), ctx.conditions);
22238
+ const name = last(pathParts);
22239
+ const decl = publicDecl(moduleAst.rootScope, name, ctx.conditions);
22244
22240
  if (decl) return {
22245
22241
  decl,
22246
22242
  moduleAst
@@ -22251,9 +22247,14 @@ function virtualModule(moduleName, ctx) {
22251
22247
  const found = ctx.virtuals?.[moduleName];
22252
22248
  if (!found) return void 0;
22253
22249
  if (found.ast) return found.ast;
22254
- const src = found.fn(ctx.conditions);
22250
+ const { conditions, rootModulePath, packageName } = ctx;
22251
+ const src = found.fn({
22252
+ conditions,
22253
+ rootModulePath,
22254
+ packageName
22255
+ });
22255
22256
  found.ast = parseSrcModule({
22256
- modulePath: ctx.packageName + "::" + moduleName,
22257
+ modulePath: packageName + "::" + moduleName,
22257
22258
  debugFilePath: moduleName,
22258
22259
  src
22259
22260
  });
@@ -22273,8 +22274,8 @@ function rootLiveDecls(decl, conditions) {
22273
22274
  assertThatDebug(scope.kind === "scope");
22274
22275
  const root = scope;
22275
22276
  if (!root._scopeDecls) {
22276
- const rootDecls = findValidRootDecls(scope, conditions);
22277
- root._scopeDecls = { decls: new Map(rootDecls.map((d) => [d.originalName, d])) };
22277
+ const decls = findValidRootDecls(scope, conditions);
22278
+ root._scopeDecls = { decls: new Map(decls.map((d) => [d.originalName, d])) };
22278
22279
  }
22279
22280
  return root._scopeDecls;
22280
22281
  }
@@ -22752,20 +22753,13 @@ async function link(params) {
22752
22753
  }
22753
22754
  /** linker api for benchmarking */
22754
22755
  function _linkSync(params) {
22755
- const { weslSrc, libs = [], packageName, debugWeslRoot } = params;
22756
- const { resolver } = params;
22757
- const resolvers = [];
22758
- if (resolver) resolvers.push(resolver);
22759
- else if (weslSrc) resolvers.push(new RecordResolver(weslSrc, {
22756
+ const { weslSrc, libs = [], packageName, debugWeslRoot, resolver } = params;
22757
+ if (!resolver && !weslSrc) throw new Error("Either resolver or weslSrc must be provided");
22758
+ const allResolvers = [resolver ?? new RecordResolver(weslSrc, {
22760
22759
  packageName,
22761
22760
  debugWeslRoot
22762
- }));
22763
- else throw new Error("Either resolver or weslSrc must be provided");
22764
- if (libs.length > 0) {
22765
- const libResolvers = createLibraryResolvers(libs, debugWeslRoot);
22766
- resolvers.push(...libResolvers);
22767
- }
22768
- const finalResolver = resolvers.length === 1 ? resolvers[0] : new CompositeResolver(resolvers);
22761
+ }), ...createLibraryResolvers(libs, debugWeslRoot)];
22762
+ const finalResolver = allResolvers.length === 1 ? allResolvers[0] : new CompositeResolver(allResolvers);
22769
22763
  return linkRegistry({
22770
22764
  ...params,
22771
22765
  resolver: finalResolver
@@ -22799,8 +22793,9 @@ function flattenLibraryTree(libs) {
22799
22793
  * that share some sources.)
22800
22794
  */
22801
22795
  function linkRegistry(params) {
22802
- const { transformedAst, newDecls, newStatements } = bindAndTransform(params);
22803
- return SrcMapBuilder.build(emitWgsl(transformedAst.moduleElem, transformedAst.srcModule, newDecls, newStatements, params.conditions));
22796
+ const { transformedAst: ast, newDecls, newStatements } = bindAndTransform(params);
22797
+ const builders = emitWgsl(ast.moduleElem, ast.srcModule, newDecls, newStatements, params.conditions);
22798
+ return SrcMapBuilder.build(builders);
22804
22799
  }
22805
22800
  /** Bind identifiers and apply transform plugins */
22806
22801
  function bindAndTransform(params) {
@@ -22848,6 +22843,7 @@ function setupVirtualLibs(virtualLibs, constants) {
22848
22843
  }
22849
22844
  return libs && mapValues(libs, (fn) => ({ fn }));
22850
22845
  }
22846
+ /** Run registered transform plugins over the bound AST. */
22851
22847
  function applyTransformPlugins(rootModule, globalNames, config) {
22852
22848
  const { moduleElem, srcModule } = rootModule;
22853
22849
  const startAst = {
@@ -22858,54 +22854,40 @@ function applyTransformPlugins(rootModule, globalNames, config) {
22858
22854
  };
22859
22855
  return filterMap(config?.plugins ?? [], (plugin) => plugin.transform).reduce((ast, transform) => transform(ast), startAst);
22860
22856
  }
22857
+ /** Assemble WGSL output from prologue statements, root module, and imported declarations. */
22861
22858
  function emitWgsl(rootModuleElem, srcModule, newDecls, newStatements, conditions = {}) {
22862
- const prologueBuilders = newStatements.map((s) => emitStatement(s, conditions));
22863
- const rootBuilder = new SrcMapBuilder({
22864
- text: srcModule.src,
22865
- path: srcModule.debugFilePath
22866
- });
22859
+ const prologueBuilders = newStatements.map((s) => emitElem(s.srcModule, s.elem, conditions, { addNl: true }));
22860
+ const rootBuilder = builderFromModule(srcModule);
22867
22861
  lowerAndEmit({
22868
22862
  srcBuilder: rootBuilder,
22869
22863
  rootElems: [rootModuleElem],
22870
22864
  conditions,
22871
22865
  extracting: false
22872
22866
  });
22873
- const declBuilders = newDecls.map((decl) => emitDecl(decl, conditions));
22867
+ const declBuilders = newDecls.map((decl) => emitElem(decl.srcModule, decl.declElem, conditions, { skipConditionalFiltering: true }));
22874
22868
  return [
22875
22869
  ...prologueBuilders,
22876
22870
  rootBuilder,
22877
22871
  ...declBuilders
22878
22872
  ];
22879
22873
  }
22880
- function emitStatement(s, conditions) {
22881
- const { elem, srcModule } = s;
22882
- const { src: text, debugFilePath: path } = srcModule;
22883
- const builder = new SrcMapBuilder({
22884
- text,
22885
- path
22886
- });
22874
+ /** Emit a single element (prologue statement or imported declaration) into a SrcMapBuilder. */
22875
+ function emitElem(srcModule, elem, conditions, opts = {}) {
22876
+ const builder = builderFromModule(srcModule);
22887
22877
  lowerAndEmit({
22888
22878
  srcBuilder: builder,
22889
22879
  rootElems: [elem],
22890
- conditions
22880
+ conditions,
22881
+ skipConditionalFiltering: opts.skipConditionalFiltering
22891
22882
  });
22892
- builder.addNl();
22883
+ if (opts.addNl) builder.addNl();
22893
22884
  return builder;
22894
22885
  }
22895
- /** Skip conditional filtering because findValidRootDecls already validated these declarations */
22896
- function emitDecl(decl, conditions) {
22897
- const { src: text, debugFilePath: path } = decl.srcModule;
22898
- const builder = new SrcMapBuilder({
22899
- text,
22900
- path
22901
- });
22902
- lowerAndEmit({
22903
- srcBuilder: builder,
22904
- rootElems: [decl.declElem],
22905
- conditions,
22906
- skipConditionalFiltering: true
22886
+ function builderFromModule(srcModule) {
22887
+ return new SrcMapBuilder({
22888
+ text: srcModule.src,
22889
+ path: srcModule.debugFilePath
22907
22890
  });
22908
- return builder;
22909
22891
  }
22910
22892
  matchOneOf(textureStorageTypes);
22911
22893
  matchOneOf(sampledTextureTypes);
@@ -27393,338 +27375,6 @@ async function hydrateBundle(path, registry, hydrated, fetcher) {
27393
27375
  return placeholder;
27394
27376
  }
27395
27377
  //#endregion
27396
- //#region ../../node_modules/.pnpm/fflate@0.8.2/node_modules/fflate/esm/browser.js
27397
- var u8 = Uint8Array, u16 = Uint16Array, i32 = Int32Array;
27398
- var fleb = new u8([
27399
- 0,
27400
- 0,
27401
- 0,
27402
- 0,
27403
- 0,
27404
- 0,
27405
- 0,
27406
- 0,
27407
- 1,
27408
- 1,
27409
- 1,
27410
- 1,
27411
- 2,
27412
- 2,
27413
- 2,
27414
- 2,
27415
- 3,
27416
- 3,
27417
- 3,
27418
- 3,
27419
- 4,
27420
- 4,
27421
- 4,
27422
- 4,
27423
- 5,
27424
- 5,
27425
- 5,
27426
- 5,
27427
- 0,
27428
- 0,
27429
- 0,
27430
- 0
27431
- ]);
27432
- var fdeb = new u8([
27433
- 0,
27434
- 0,
27435
- 0,
27436
- 0,
27437
- 1,
27438
- 1,
27439
- 2,
27440
- 2,
27441
- 3,
27442
- 3,
27443
- 4,
27444
- 4,
27445
- 5,
27446
- 5,
27447
- 6,
27448
- 6,
27449
- 7,
27450
- 7,
27451
- 8,
27452
- 8,
27453
- 9,
27454
- 9,
27455
- 10,
27456
- 10,
27457
- 11,
27458
- 11,
27459
- 12,
27460
- 12,
27461
- 13,
27462
- 13,
27463
- 0,
27464
- 0
27465
- ]);
27466
- var clim = new u8([
27467
- 16,
27468
- 17,
27469
- 18,
27470
- 0,
27471
- 8,
27472
- 7,
27473
- 9,
27474
- 6,
27475
- 10,
27476
- 5,
27477
- 11,
27478
- 4,
27479
- 12,
27480
- 3,
27481
- 13,
27482
- 2,
27483
- 14,
27484
- 1,
27485
- 15
27486
- ]);
27487
- var freb = function(eb, start) {
27488
- var b = new u16(31);
27489
- for (var i = 0; i < 31; ++i) b[i] = start += 1 << eb[i - 1];
27490
- var r = new i32(b[30]);
27491
- for (var i = 1; i < 30; ++i) for (var j = b[i]; j < b[i + 1]; ++j) r[j] = j - b[i] << 5 | i;
27492
- return {
27493
- b,
27494
- r
27495
- };
27496
- };
27497
- var _a = freb(fleb, 2), fl = _a.b, revfl = _a.r;
27498
- fl[28] = 258, revfl[258] = 28;
27499
- var _b = freb(fdeb, 0), fd = _b.b;
27500
- _b.r;
27501
- var rev = new u16(32768);
27502
- for (var i = 0; i < 32768; ++i) {
27503
- var x = (i & 43690) >> 1 | (i & 21845) << 1;
27504
- x = (x & 52428) >> 2 | (x & 13107) << 2;
27505
- x = (x & 61680) >> 4 | (x & 3855) << 4;
27506
- rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1;
27507
- }
27508
- var hMap = (function(cd, mb, r) {
27509
- var s = cd.length;
27510
- var i = 0;
27511
- var l = new u16(mb);
27512
- for (; i < s; ++i) if (cd[i]) ++l[cd[i] - 1];
27513
- var le = new u16(mb);
27514
- for (i = 1; i < mb; ++i) le[i] = le[i - 1] + l[i - 1] << 1;
27515
- var co;
27516
- if (r) {
27517
- co = new u16(1 << mb);
27518
- var rvb = 15 - mb;
27519
- for (i = 0; i < s; ++i) if (cd[i]) {
27520
- var sv = i << 4 | cd[i];
27521
- var r_1 = mb - cd[i];
27522
- var v = le[cd[i] - 1]++ << r_1;
27523
- for (var m = v | (1 << r_1) - 1; v <= m; ++v) co[rev[v] >> rvb] = sv;
27524
- }
27525
- } else {
27526
- co = new u16(s);
27527
- for (i = 0; i < s; ++i) if (cd[i]) co[i] = rev[le[cd[i] - 1]++] >> 15 - cd[i];
27528
- }
27529
- return co;
27530
- });
27531
- var flt = new u8(288);
27532
- for (var i = 0; i < 144; ++i) flt[i] = 8;
27533
- for (var i = 144; i < 256; ++i) flt[i] = 9;
27534
- for (var i = 256; i < 280; ++i) flt[i] = 7;
27535
- for (var i = 280; i < 288; ++i) flt[i] = 8;
27536
- var fdt = new u8(32);
27537
- for (var i = 0; i < 32; ++i) fdt[i] = 5;
27538
- var flrm = /* @__PURE__ */ hMap(flt, 9, 1), fdrm = /* @__PURE__ */ hMap(fdt, 5, 1);
27539
- var max = function(a) {
27540
- var m = a[0];
27541
- for (var i = 1; i < a.length; ++i) if (a[i] > m) m = a[i];
27542
- return m;
27543
- };
27544
- var bits = function(d, p, m) {
27545
- var o = p / 8 | 0;
27546
- return (d[o] | d[o + 1] << 8) >> (p & 7) & m;
27547
- };
27548
- var bits16 = function(d, p) {
27549
- var o = p / 8 | 0;
27550
- return (d[o] | d[o + 1] << 8 | d[o + 2] << 16) >> (p & 7);
27551
- };
27552
- var shft = function(p) {
27553
- return (p + 7) / 8 | 0;
27554
- };
27555
- var slc = function(v, s, e) {
27556
- if (s == null || s < 0) s = 0;
27557
- if (e == null || e > v.length) e = v.length;
27558
- return new u8(v.subarray(s, e));
27559
- };
27560
- var ec = [
27561
- "unexpected EOF",
27562
- "invalid block type",
27563
- "invalid length/literal",
27564
- "invalid distance",
27565
- "stream finished",
27566
- "no stream handler",
27567
- ,
27568
- "no callback",
27569
- "invalid UTF-8 data",
27570
- "extra field too long",
27571
- "date not in range 1980-2099",
27572
- "filename too long",
27573
- "stream finishing",
27574
- "invalid zip data"
27575
- ];
27576
- var err = function(ind, msg, nt) {
27577
- var e = new Error(msg || ec[ind]);
27578
- e.code = ind;
27579
- if (Error.captureStackTrace) Error.captureStackTrace(e, err);
27580
- if (!nt) throw e;
27581
- return e;
27582
- };
27583
- var inflt = function(dat, st, buf, dict) {
27584
- var sl = dat.length, dl = dict ? dict.length : 0;
27585
- if (!sl || st.f && !st.l) return buf || new u8(0);
27586
- var noBuf = !buf;
27587
- var resize = noBuf || st.i != 2;
27588
- var noSt = st.i;
27589
- if (noBuf) buf = new u8(sl * 3);
27590
- var cbuf = function(l) {
27591
- var bl = buf.length;
27592
- if (l > bl) {
27593
- var nbuf = new u8(Math.max(bl * 2, l));
27594
- nbuf.set(buf);
27595
- buf = nbuf;
27596
- }
27597
- };
27598
- var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n;
27599
- var tbts = sl * 8;
27600
- do {
27601
- if (!lm) {
27602
- final = bits(dat, pos, 1);
27603
- var type = bits(dat, pos + 1, 3);
27604
- pos += 3;
27605
- if (!type) {
27606
- var s = shft(pos) + 4, l = dat[s - 4] | dat[s - 3] << 8, t = s + l;
27607
- if (t > sl) {
27608
- if (noSt) err(0);
27609
- break;
27610
- }
27611
- if (resize) cbuf(bt + l);
27612
- buf.set(dat.subarray(s, t), bt);
27613
- st.b = bt += l, st.p = pos = t * 8, st.f = final;
27614
- continue;
27615
- } else if (type == 1) lm = flrm, dm = fdrm, lbt = 9, dbt = 5;
27616
- else if (type == 2) {
27617
- var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4;
27618
- var tl = hLit + bits(dat, pos + 5, 31) + 1;
27619
- pos += 14;
27620
- var ldt = new u8(tl);
27621
- var clt = new u8(19);
27622
- for (var i = 0; i < hcLen; ++i) clt[clim[i]] = bits(dat, pos + i * 3, 7);
27623
- pos += hcLen * 3;
27624
- var clb = max(clt), clbmsk = (1 << clb) - 1;
27625
- var clm = hMap(clt, clb, 1);
27626
- for (var i = 0; i < tl;) {
27627
- var r = clm[bits(dat, pos, clbmsk)];
27628
- pos += r & 15;
27629
- var s = r >> 4;
27630
- if (s < 16) ldt[i++] = s;
27631
- else {
27632
- var c = 0, n = 0;
27633
- if (s == 16) n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1];
27634
- else if (s == 17) n = 3 + bits(dat, pos, 7), pos += 3;
27635
- else if (s == 18) n = 11 + bits(dat, pos, 127), pos += 7;
27636
- while (n--) ldt[i++] = c;
27637
- }
27638
- }
27639
- var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);
27640
- lbt = max(lt);
27641
- dbt = max(dt);
27642
- lm = hMap(lt, lbt, 1);
27643
- dm = hMap(dt, dbt, 1);
27644
- } else err(1);
27645
- if (pos > tbts) {
27646
- if (noSt) err(0);
27647
- break;
27648
- }
27649
- }
27650
- if (resize) cbuf(bt + 131072);
27651
- var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1;
27652
- var lpos = pos;
27653
- for (;; lpos = pos) {
27654
- var c = lm[bits16(dat, pos) & lms], sym = c >> 4;
27655
- pos += c & 15;
27656
- if (pos > tbts) {
27657
- if (noSt) err(0);
27658
- break;
27659
- }
27660
- if (!c) err(2);
27661
- if (sym < 256) buf[bt++] = sym;
27662
- else if (sym == 256) {
27663
- lpos = pos, lm = null;
27664
- break;
27665
- } else {
27666
- var add = sym - 254;
27667
- if (sym > 264) {
27668
- var i = sym - 257, b = fleb[i];
27669
- add = bits(dat, pos, (1 << b) - 1) + fl[i];
27670
- pos += b;
27671
- }
27672
- var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;
27673
- if (!d) err(3);
27674
- pos += d & 15;
27675
- var dt = fd[dsym];
27676
- if (dsym > 3) {
27677
- var b = fdeb[dsym];
27678
- dt += bits16(dat, pos) & (1 << b) - 1, pos += b;
27679
- }
27680
- if (pos > tbts) {
27681
- if (noSt) err(0);
27682
- break;
27683
- }
27684
- if (resize) cbuf(bt + 131072);
27685
- var end = bt + add;
27686
- if (bt < dt) {
27687
- var shift = dl - dt, dend = Math.min(dt, end);
27688
- if (shift + bt < 0) err(3);
27689
- for (; bt < dend; ++bt) buf[bt] = dict[shift + bt];
27690
- }
27691
- for (; bt < end; ++bt) buf[bt] = buf[bt - dt];
27692
- }
27693
- }
27694
- st.l = lm, st.p = lpos, st.b = bt, st.f = final;
27695
- if (lm) final = 1, st.m = lbt, st.d = dm, st.n = dbt;
27696
- } while (!final);
27697
- return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt);
27698
- };
27699
- var et = /* @__PURE__ */ new u8(0);
27700
- var gzs = function(d) {
27701
- if (d[0] != 31 || d[1] != 139 || d[2] != 8) err(6, "invalid gzip data");
27702
- var flg = d[3];
27703
- var st = 10;
27704
- if (flg & 4) st += (d[10] | d[11] << 8) + 2;
27705
- for (var zs = (flg >> 3 & 1) + (flg >> 4 & 1); zs > 0; zs -= !d[st++]);
27706
- return st + (flg & 2);
27707
- };
27708
- var gzl = function(d) {
27709
- var l = d.length;
27710
- return (d[l - 4] | d[l - 3] << 8 | d[l - 2] << 16 | d[l - 1] << 24) >>> 0;
27711
- };
27712
- /**
27713
- * Expands GZIP data
27714
- * @param data The data to decompress
27715
- * @param opts The decompression options
27716
- * @returns The decompressed version of the data
27717
- */
27718
- function gunzipSync(data, opts) {
27719
- var st = gzs(data);
27720
- if (st + 8 > data.length) err(6, "invalid gzip data");
27721
- return inflt(data.subarray(st, -8), { i: 2 }, opts && opts.out || new u8(gzl(data)), opts && opts.dictionary);
27722
- }
27723
- var td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder();
27724
- try {
27725
- td.decode(et, { stream: true });
27726
- } catch (e) {}
27727
- //#endregion
27728
27378
  //#region ../../node_modules/.pnpm/nanotar@0.2.0/node_modules/nanotar/dist/index.mjs
27729
27379
  const TAR_TYPE_FILE = 0;
27730
27380
  const TAR_TYPE_DIR = 5;
@@ -27776,6 +27426,13 @@ function parseTar(data, opts) {
27776
27426
  }
27777
27427
  return files;
27778
27428
  }
27429
+ async function parseTarGzip(data, opts = {}) {
27430
+ const stream = new ReadableStream({ start(controller) {
27431
+ controller.enqueue(new Uint8Array(data));
27432
+ controller.close();
27433
+ } }).pipeThrough(new DecompressionStream(opts.compression ?? "gzip"));
27434
+ return parseTar(await new Response(stream).arrayBuffer(), opts);
27435
+ }
27779
27436
  function _readString(buffer, offset, size) {
27780
27437
  const view = new Uint8Array(buffer, offset, size);
27781
27438
  const i = view.indexOf(0);
@@ -27812,7 +27469,7 @@ function memoAsync(fn) {
27812
27469
  async function fetchAndExtractTgzRaw(tgzUrl) {
27813
27470
  const response = await fetch(tgzUrl);
27814
27471
  if (!response.ok) throw new Error(`Failed to fetch package: HTTP ${response.status}`);
27815
- return parseTar(gunzipSync(new Uint8Array(await response.arrayBuffer())));
27472
+ return parseTarGzip(new Uint8Array(await response.arrayBuffer()));
27816
27473
  }
27817
27474
  async function npmPackageToUrlRaw(packageName) {
27818
27475
  const metadataUrl = `https://registry.npmjs.org/${packageName}`;
@@ -27879,6 +27536,10 @@ async function fetchOnePackage(pkgId, loaded) {
27879
27536
  throw new Error(`Package not found: ${pkgId}`);
27880
27537
  }
27881
27538
  //#endregion
27539
+ //#region src/SaveEndpoint.ts
27540
+ /** Shared between client (WgslEdit) and server (SaveMiddleware) so they can't drift. */
27541
+ const saveEndpoint = "/__wgsl-edit/save";
27542
+ //#endregion
27882
27543
  //#region src/WgslEdit.css?inline
27883
27544
  var WgslEdit_default = ":host {\n --tab-bar-bg: transparent;\n --tab-border: #ccc;\n --tab-color: #999;\n --tab-active-bg: #fff;\n --tab-active-color: #222;\n --tab-accent: #5f61d8;\n\n display: flex;\n flex-direction: column;\n position: relative;\n min-height: 100px;\n}\n\n:host(.dark) {\n --tab-bar-bg: transparent;\n --tab-border: #555;\n --tab-color: #999;\n --tab-active-bg: #1e1e1e;\n --tab-active-color: #fff;\n}\n\n.tab-bar {\n --bar-v: 6px;\n --bar-h: 8px;\n display: flex;\n align-items: center;\n gap: 14px;\n padding: var(--bar-v) var(--bar-h);\n background: var(--tab-bar-bg);\n flex-shrink: 0;\n position: relative;\n}\n\n.tab-bar::after {\n content: \"\";\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 1px;\n background: var(--tab-border);\n}\n\n.tab {\n --tab-v: 5px;\n --tab-h: 12px;\n display: flex;\n align-items: center;\n gap: 16px;\n padding: var(--tab-v) var(--tab-h);\n background: transparent;\n border: none;\n border-radius: 0;\n color: var(--tab-color);\n cursor: pointer;\n position: relative;\n font-size: 15px;\n font-family: system-ui, sans-serif;\n}\n\n.tab.active {\n color: var(--tab-accent);\n font-weight: 600;\n padding-bottom: calc(var(--tab-v) + var(--bar-v) + 0.5px);\n margin-bottom: calc(-1 * (var(--bar-v) + 0.5px));\n position: relative;\n z-index: 1;\n border-bottom: 2px solid var(--tab-accent);\n}\n\n.tab.active:first-child {\n margin-left: calc(-1 * var(--bar-h));\n padding-left: calc(var(--tab-h) + var(--bar-h));\n}\n\n.tab-close {\n position: absolute;\n right: -8px;\n /* top: 2px; */\n width: 16px;\n height: 16px;\n padding: 0;\n border: none;\n background: transparent;\n color: inherit;\n cursor: pointer;\n opacity: 0;\n font-size: 18px;\n line-height: 1;\n}\n\n.tab:hover .tab-close {\n opacity: 0.6;\n}\n\n.tab-close:hover {\n opacity: 1;\n}\n\n.tab-rename {\n background: var(--tab-active-bg);\n border: 1px solid var(--tab-border);\n border-radius: 4px;\n color: var(--tab-active-color);\n font-size: 15px;\n font-family: system-ui, sans-serif;\n padding: 0 4px;\n outline: none;\n}\n\n.tab-add {\n padding: 0 10px;\n background: transparent;\n border: none;\n color: var(--tab-color);\n cursor: pointer;\n font-size: 25px;\n line-height: 1;\n}\n\n.tab-add:hover {\n color: var(--tab-active-color);\n}\n\n.editor-container {\n flex: 1;\n min-height: 0;\n width: 100%;\n padding: var(--editor-padding, 16px 0 0);\n}\n\n.cm-editor {\n height: 100%;\n font-size: var(--editor-font-size, 14px);\n}\n\n.cm-scroller {\n overflow: auto;\n}\n\n.snackbar {\n position: absolute;\n bottom: 16px;\n left: 16px;\n background: #e8e8e8;\n color: #333;\n padding: 10px 20px;\n border-radius: 8px;\n font-size: 14px;\n font-family: system-ui, sans-serif;\n line-height: 1.4;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s;\n z-index: 10;\n}\n\n:host(.dark) .snackbar {\n background: #3a3a3a;\n color: #e0e0e0;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);\n}\n\n.snackbar.visible {\n opacity: 1;\n}\n";
27884
27545
  //#endregion
@@ -27917,7 +27578,8 @@ var WgslEdit = class extends HTMLElement {
27917
27578
  "lint-from",
27918
27579
  "line-numbers",
27919
27580
  "fetch-libs",
27920
- "gpu-lint"
27581
+ "gpu-lint",
27582
+ "autosave"
27921
27583
  ];
27922
27584
  editorView = null;
27923
27585
  editorContainer;
@@ -27946,6 +27608,10 @@ var WgslEdit = class extends HTMLElement {
27946
27608
  _fetchingPkgs = /* @__PURE__ */ new Set();
27947
27609
  _fetchedPkgs = /* @__PURE__ */ new Set();
27948
27610
  _snackTimer;
27611
+ _devSaveListener = null;
27612
+ _saveTimer;
27613
+ _dirtyFiles = /* @__PURE__ */ new Set();
27614
+ _switchingFile = false;
27949
27615
  _externalDiagnostics = [];
27950
27616
  _lintFromEl = null;
27951
27617
  /** Bound listeners for lint-from element's compile events. */
@@ -27969,10 +27635,13 @@ var WgslEdit = class extends HTMLElement {
27969
27635
  connectedCallback() {
27970
27636
  this.initEditor();
27971
27637
  this.loadInitialContent();
27972
- upgradeProperty(this, "conditions");
27973
- upgradeProperty(this, "source");
27974
- upgradeProperty(this, "sources");
27975
- upgradeProperty(this, "project");
27638
+ for (const p of [
27639
+ "conditions",
27640
+ "source",
27641
+ "sources",
27642
+ "project",
27643
+ "autosave"
27644
+ ]) upgradeProperty(this, p);
27976
27645
  }
27977
27646
  disconnectedCallback() {
27978
27647
  this.connectLintSource(null);
@@ -27980,25 +27649,43 @@ var WgslEdit = class extends HTMLElement {
27980
27649
  this.editorView = null;
27981
27650
  }
27982
27651
  attributeChangedCallback(name, _old, value) {
27983
- if (name === "src" && value && this.editorView) this.loadFromUrl(value);
27984
- else if (name === "readonly") this.updateReadonly();
27985
- else if (name === "theme") this.theme = value || "auto";
27986
- else if (name === "tabs") {
27987
- this._tabs = value !== "false";
27988
- this.renderTabs();
27989
- } else if (name === "lint") {
27990
- this._lint = value || "on";
27991
- this.updateLint();
27992
- } else if (name === "lint-from") this.connectLintSource(value);
27993
- else if (name === "line-numbers") {
27994
- this._lineNumbers = value === "true";
27995
- this.updateLineNumbers();
27996
- } else if (name === "fetch-libs") {
27997
- this._fetchLibs = value !== "false";
27998
- this.updateLint();
27999
- } else if (name === "gpu-lint") {
28000
- this._gpuLint = value !== "off";
28001
- this.updateLint();
27652
+ switch (name) {
27653
+ case "src":
27654
+ if (value && this.editorView) this.loadFromUrl(value);
27655
+ break;
27656
+ case "readonly":
27657
+ this.updateReadonly();
27658
+ break;
27659
+ case "theme":
27660
+ this.theme = value || "auto";
27661
+ break;
27662
+ case "tabs":
27663
+ this._tabs = value !== "false";
27664
+ this.renderTabs();
27665
+ break;
27666
+ case "lint":
27667
+ this._lint = value || "on";
27668
+ this.updateLint();
27669
+ break;
27670
+ case "lint-from":
27671
+ this.connectLintSource(value);
27672
+ break;
27673
+ case "line-numbers":
27674
+ this._lineNumbers = value === "true";
27675
+ this.updateLineNumbers();
27676
+ break;
27677
+ case "fetch-libs":
27678
+ this._fetchLibs = value !== "false";
27679
+ this.updateLint();
27680
+ break;
27681
+ case "gpu-lint":
27682
+ this._gpuLint = value !== "off";
27683
+ this.updateLint();
27684
+ break;
27685
+ case "autosave":
27686
+ if (value !== null && value !== "false") this.enableDevSave();
27687
+ else this.disableDevSave();
27688
+ break;
28002
27689
  }
28003
27690
  }
28004
27691
  /** Conditions for conditional compilation (@if/@elif/@else). */
@@ -28010,6 +27697,10 @@ var WgslEdit = class extends HTMLElement {
28010
27697
  this.updateLint();
28011
27698
  this.dispatchChange();
28012
27699
  }
27700
+ /** Reconfigure a compartment via the active editor view (no-op if unmounted). */
27701
+ reconfigure(comp, ext) {
27702
+ this.editorView?.dispatch({ effects: comp.reconfigure(ext) });
27703
+ }
28013
27704
  /** Active file content (single-file API). */
28014
27705
  get source() {
28015
27706
  return this.editorView?.state.doc.toString() ?? this._pendingSource ?? "";
@@ -28017,27 +27708,30 @@ var WgslEdit = class extends HTMLElement {
28017
27708
  /** Set active file content (single-file API). Auto-creates a default file entry. */
28018
27709
  set source(value) {
28019
27710
  if (!this._activeFile && this._files.size === 0) {
28020
- this._files.set("main.wesl", { doc: Text.of(value.split("\n")) });
27711
+ this._files.set("main.wesl", { doc: toDoc(value) });
28021
27712
  this._activeFile = "main.wesl";
28022
27713
  this.renderTabs();
28023
27714
  }
28024
27715
  if (this.editorView) {
28025
- const to = this.editorView.state.doc.length;
28026
- this.editorView.dispatch({ changes: {
28027
- from: 0,
28028
- to,
28029
- insert: value
28030
- } });
27716
+ const docLength = this.editorView.state.doc.length;
27717
+ this.editorView.dispatch({
27718
+ changes: {
27719
+ from: 0,
27720
+ to: docLength,
27721
+ insert: value
27722
+ },
27723
+ annotations: Transaction.addToHistory.of(false)
27724
+ });
28031
27725
  } else {
28032
27726
  this._pendingSource = value;
28033
27727
  const entry = this._files.get(this._activeFile);
28034
- if (entry) entry.doc = Text.of(value.split("\n"));
27728
+ if (entry) entry.doc = toDoc(value);
28035
27729
  }
28036
27730
  }
28037
27731
  /** All file contents keyed by module path (e.g., "package::main"). */
28038
27732
  get sources() {
28039
27733
  this.saveCurrentFileState();
28040
- const pkg = this._packageName ?? "package";
27734
+ const pkg = this.pkgName();
28041
27735
  const result = {};
28042
27736
  for (const [tabName, state] of this._files) result[fileToModulePath(tabName, pkg, false)] = state.doc.toString();
28043
27737
  return result;
@@ -28047,12 +27741,13 @@ var WgslEdit = class extends HTMLElement {
28047
27741
  this._files.clear();
28048
27742
  for (const [key, content] of Object.entries(value)) {
28049
27743
  const tabName = toTabName(key);
28050
- this._files.set(tabName, { doc: Text.of(content.split("\n")) });
27744
+ this._files.set(tabName, { doc: toDoc(content) });
28051
27745
  }
28052
27746
  const firstKey = Object.keys(value)[0];
28053
27747
  if (firstKey) this.switchToFile(toTabName(firstKey));
28054
27748
  this.renderTabs();
28055
27749
  }
27750
+ /** Snapshot of all editor state needed to link: sources, conditions, libs, root module. */
28056
27751
  get project() {
28057
27752
  return {
28058
27753
  weslSrc: this.sources,
@@ -28060,20 +27755,22 @@ var WgslEdit = class extends HTMLElement {
28060
27755
  conditions: this._conditions,
28061
27756
  constants: this._constants,
28062
27757
  libs: this._libs,
28063
- packageName: this._packageName
27758
+ packageName: this._packageName,
27759
+ shaderRoot: this.shaderRoot ?? void 0
28064
27760
  };
28065
27761
  }
28066
27762
  set project(value) {
28067
27763
  const { weslSrc, rootModuleName, conditions } = value;
28068
- const { constants, packageName, libs } = value;
27764
+ const { constants, packageName, libs, shaderRoot } = value;
28069
27765
  if (conditions !== void 0) this._conditions = conditions;
28070
27766
  if (constants !== void 0) this._constants = constants;
28071
27767
  if (packageName !== void 0) this._packageName = packageName;
28072
27768
  if (libs !== void 0) this._libs = libs;
28073
27769
  if (rootModuleName !== void 0) this._rootModuleName = rootModuleName;
27770
+ if (shaderRoot !== void 0 && !this.hasAttribute("shader-root")) this.shaderRoot = shaderRoot;
28074
27771
  if (weslSrc) {
28075
27772
  this.sources = weslSrc;
28076
- const tab = toTabName(rootModuleName ?? this._rootModuleName ?? "");
27773
+ const tab = toTabName(this._rootModuleName ?? "");
28077
27774
  if (tab) this.activeFile = tab;
28078
27775
  }
28079
27776
  this.updateLint();
@@ -28086,14 +27783,13 @@ var WgslEdit = class extends HTMLElement {
28086
27783
  })).dest;
28087
27784
  }
28088
27785
  linkParams() {
28089
- const pkg = this._packageName ?? "package";
28090
27786
  return {
28091
27787
  weslSrc: this.sources,
28092
- rootModuleName: this._rootModuleName ?? fileToModulePath(this._activeFile, pkg, false),
27788
+ rootModuleName: this._rootModuleName ?? this.activeModulePath(),
28093
27789
  conditions: this._conditions,
28094
27790
  constants: this._constants,
28095
27791
  libs: this._libs,
28096
- packageName: pkg
27792
+ packageName: this.pkgName()
28097
27793
  };
28098
27794
  }
28099
27795
  get libs() {
@@ -28149,6 +27845,20 @@ var WgslEdit = class extends HTMLElement {
28149
27845
  if (!value) this.setAttribute("gpu-lint", "off");
28150
27846
  else this.removeAttribute("gpu-lint");
28151
27847
  }
27848
+ /** Persist edits to disk via the dev-server save endpoint.
27849
+ * Requires `wgslEditAutosave()` to be installed in vite.config.ts. */
27850
+ get autosave() {
27851
+ return this._devSaveListener !== null;
27852
+ }
27853
+ set autosave(value) {
27854
+ if (value) {
27855
+ this.enableDevSave();
27856
+ this.setAttribute("autosave", "");
27857
+ } else {
27858
+ this.disableDevSave();
27859
+ this.removeAttribute("autosave");
27860
+ }
27861
+ }
28152
27862
  /** Whether to auto-fetch missing library packages from npm (default: true). */
28153
27863
  get fetchLibs() {
28154
27864
  return this._fetchLibs;
@@ -28198,13 +27908,15 @@ var WgslEdit = class extends HTMLElement {
28198
27908
  if (value) this.setAttribute("shader-root", value);
28199
27909
  else this.removeAttribute("shader-root");
28200
27910
  }
27911
+ /** Add a new file and switch to it. No-op if `name` already exists. */
28201
27912
  addFile(name, content = "") {
28202
27913
  if (this._files.has(name)) return;
28203
- this._files.set(name, { doc: Text.of(content.split("\n")) });
27914
+ this._files.set(name, { doc: toDoc(content) });
28204
27915
  this.switchToFile(name);
28205
27916
  this.renderTabs();
28206
27917
  this.dispatchFileChange("add", name);
28207
27918
  }
27919
+ /** Remove a file. No-op if it is missing or is the last remaining file. */
28208
27920
  removeFile(name) {
28209
27921
  if (!this._files.has(name) || this._files.size <= 1) return;
28210
27922
  this._files.delete(name);
@@ -28215,6 +27927,7 @@ var WgslEdit = class extends HTMLElement {
28215
27927
  this.renderTabs();
28216
27928
  this.dispatchFileChange("remove", name);
28217
27929
  }
27930
+ /** Rename a file, preserving its document and editor state. No-op on collision. */
28218
27931
  renameFile(oldName, newName) {
28219
27932
  const state = this._files.get(oldName);
28220
27933
  if (!state || this._files.has(newName)) return;
@@ -28230,28 +27943,36 @@ var WgslEdit = class extends HTMLElement {
28230
27943
  this.saveCurrentFileState();
28231
27944
  this._activeFile = name;
28232
27945
  const fileState = this._files.get(name);
28233
- if (this.editorView) {
28234
- const changes = {
28235
- from: 0,
28236
- to: this.editorView.state.doc.length,
28237
- insert: fileState.doc.toString()
28238
- };
28239
- const effects = EditorView.scrollIntoView(fileState.scrollPos ?? 0);
28240
- this.editorView.dispatch({
28241
- changes,
28242
- selection: fileState.selection,
28243
- effects
28244
- });
27946
+ const view = this.editorView;
27947
+ if (view) {
27948
+ const state = fileState.state ?? this.createFileState(fileState.doc);
27949
+ fileState.state = state;
27950
+ view.setState(state);
27951
+ if (fileState.scrollPos != null) view.scrollDOM.scrollTop = fileState.scrollPos;
28245
27952
  }
28246
27953
  this.renderTabs();
28247
27954
  }
27955
+ /** Build a fresh EditorState seeded with `doc` and the current extension set. */
27956
+ createFileState(doc) {
27957
+ return EditorState.create({
27958
+ doc,
27959
+ extensions: this.buildExtensions()
27960
+ });
27961
+ }
28248
27962
  saveCurrentFileState() {
28249
27963
  const view = this.editorView;
28250
- const state = this._activeFile ? this._files.get(this._activeFile) : void 0;
28251
- if (!view || !state) return;
28252
- state.doc = view.state.doc;
28253
- state.selection = view.state.selection;
28254
- state.scrollPos = view.scrollDOM.scrollTop;
27964
+ const fileState = this._activeFile ? this._files.get(this._activeFile) : void 0;
27965
+ if (!view || !fileState) return;
27966
+ fileState.state = view.state;
27967
+ fileState.doc = view.state.doc;
27968
+ fileState.selection = view.state.selection;
27969
+ fileState.scrollPos = view.scrollDOM.scrollTop;
27970
+ }
27971
+ pkgName() {
27972
+ return this._packageName ?? "package";
27973
+ }
27974
+ activeModulePath() {
27975
+ return fileToModulePath(this._activeFile, this.pkgName(), false);
28255
27976
  }
28256
27977
  dispatchChange() {
28257
27978
  this.dispatchEvent(new CustomEvent("change", { detail: this.project }));
@@ -28263,25 +27984,86 @@ var WgslEdit = class extends HTMLElement {
28263
27984
  };
28264
27985
  this.dispatchEvent(new CustomEvent("file-change", { detail }));
28265
27986
  }
27987
+ scheduleAutosave() {
27988
+ this._dirtyFiles.add(this._activeFile);
27989
+ clearTimeout(this._saveTimer);
27990
+ this._saveTimer = setTimeout(() => this.fireAutosave(), 500);
27991
+ }
27992
+ /** Dispatch an `autosave` event with a fresh project snapshot and the dirty-file list. */
27993
+ fireAutosave() {
27994
+ const dirty = [...this._dirtyFiles];
27995
+ this._dirtyFiles.clear();
27996
+ const detail = {
27997
+ project: this.project,
27998
+ dirty
27999
+ };
28000
+ this.dispatchEvent(new CustomEvent("autosave", { detail }));
28001
+ }
28002
+ enableDevSave() {
28003
+ if (this._devSaveListener) return;
28004
+ this._devSaveListener = (e) => this.devSave(e);
28005
+ this.addEventListener("autosave", this._devSaveListener);
28006
+ }
28007
+ disableDevSave() {
28008
+ if (!this._devSaveListener) return;
28009
+ this.removeEventListener("autosave", this._devSaveListener);
28010
+ this._devSaveListener = null;
28011
+ }
28012
+ /** Built-in `autosave` listener: POST each dirty file to the dev-server save endpoint. */
28013
+ async devSave(e) {
28014
+ const root = this.shaderRoot;
28015
+ if (!root) return;
28016
+ const { project, dirty } = e.detail;
28017
+ const weslSrc = project.weslSrc;
28018
+ if (!weslSrc) return;
28019
+ const pkg = this.pkgName();
28020
+ for (const file of dirty) {
28021
+ const content = weslSrc[fileToModulePath(file, pkg, false)];
28022
+ if (content === void 0) continue;
28023
+ try {
28024
+ const res = await fetch(saveEndpoint, {
28025
+ method: "POST",
28026
+ headers: { "Content-Type": "application/json" },
28027
+ body: JSON.stringify({
28028
+ root,
28029
+ file,
28030
+ content
28031
+ })
28032
+ });
28033
+ if (!res.ok) return this.devSaveFailed(`HTTP ${res.status}`);
28034
+ } catch (err) {
28035
+ return this.devSaveFailed(err instanceof Error ? err.message : String(err));
28036
+ }
28037
+ }
28038
+ }
28039
+ /** One-shot disable on first failure: avoids spamming the console on every keystroke. */
28040
+ devSaveFailed(reason) {
28041
+ this.disableDevSave();
28042
+ console.error(`wgsl-edit: autosave disabled (${reason}). Make sure wgslEditAutosave() is installed in vite.config.ts.`);
28043
+ }
28044
+ /** First-time setup: read attributes, parse inline content, mount the EditorView. */
28266
28045
  initEditor() {
28267
28046
  this.readInitialAttributes();
28268
28047
  this.parseInlineContent();
28269
28048
  this._mediaQuery = matchMedia("(prefers-color-scheme: dark)");
28270
28049
  this._mediaQuery.addEventListener("change", () => this.updateTheme());
28271
28050
  const firstFile = this._files.keys().next().value;
28272
- const initialDoc = this._pendingSource ?? (firstFile ? this._files.get(firstFile).doc.toString() : "");
28051
+ const firstDoc = firstFile && this._files.get(firstFile).doc.toString();
28052
+ const initialDoc = this._pendingSource ?? firstDoc ?? "";
28273
28053
  this._pendingSource = null;
28274
28054
  if (firstFile) this._activeFile = firstFile;
28275
28055
  else if (initialDoc) {
28276
- this._files.set("main.wesl", { doc: Text.of(initialDoc.split("\n")) });
28056
+ this._files.set("main.wesl", { doc: toDoc(initialDoc) });
28277
28057
  this._activeFile = "main.wesl";
28278
28058
  }
28279
- const extensions = this.buildExtensions();
28059
+ const state = this.createFileState(initialDoc);
28060
+ const active = this._activeFile ? this._files.get(this._activeFile) : null;
28061
+ if (active) {
28062
+ active.state = state;
28063
+ active.doc = state.doc;
28064
+ }
28280
28065
  this.editorView = new EditorView({
28281
- state: EditorState.create({
28282
- doc: initialDoc,
28283
- extensions
28284
- }),
28066
+ state,
28285
28067
  parent: this.editorContainer
28286
28068
  });
28287
28069
  this.renderTabs();
@@ -28298,6 +28080,7 @@ var WgslEdit = class extends HTMLElement {
28298
28080
  const lintFromAttr = this.getAttribute("lint-from");
28299
28081
  if (lintFromAttr) this.connectLintSource(lintFromAttr);
28300
28082
  }
28083
+ /** Assemble the full CodeMirror extension set for a fresh EditorState. */
28301
28084
  buildExtensions() {
28302
28085
  const baseTheme = EditorView.theme({
28303
28086
  ".cm-content": { padding: "0" },
@@ -28342,6 +28125,7 @@ var WgslEdit = class extends HTMLElement {
28342
28125
  this._externalDiagnostics = [];
28343
28126
  this.saveCurrentFileState();
28344
28127
  this.dispatchChange();
28128
+ if (!this._switchingFile) this.scheduleAutosave();
28345
28129
  }
28346
28130
  })
28347
28131
  ];
@@ -28352,11 +28136,11 @@ var WgslEdit = class extends HTMLElement {
28352
28136
  return [EditorView.theme({}, { dark: isDark }), isDark ? darkColors : lightColors];
28353
28137
  }
28354
28138
  updateTheme() {
28355
- this.editorView?.dispatch({ effects: this.themeCompartment.reconfigure(this.resolveTheme()) });
28139
+ this.reconfigure(this.themeCompartment, this.resolveTheme());
28356
28140
  }
28357
28141
  updateReadonly() {
28358
28142
  const ext = EditorState.readOnly.of(this.readonly);
28359
- this.editorView?.dispatch({ effects: this.readonlyCompartment.reconfigure(ext) });
28143
+ this.reconfigure(this.readonlyCompartment, ext);
28360
28144
  this.renderTabs();
28361
28145
  }
28362
28146
  resolveLint() {
@@ -28364,7 +28148,7 @@ var WgslEdit = class extends HTMLElement {
28364
28148
  const useGpuLint = this._gpuLint && !this._lintFromEl;
28365
28149
  return createWeslLinter({
28366
28150
  getSources: () => this.sources,
28367
- rootModule: () => fileToModulePath(this._activeFile, this._packageName ?? "package", false),
28151
+ rootModule: () => this.activeModulePath(),
28368
28152
  conditions: () => this._conditions,
28369
28153
  packageName: () => this._packageName,
28370
28154
  getExternalDiagnostics: () => this._externalDiagnostics,
@@ -28377,11 +28161,8 @@ var WgslEdit = class extends HTMLElement {
28377
28161
  /** Link WESL->WGSL and validate via WebGPU, returning CodeMirror diagnostics. */
28378
28162
  async gpuValidate() {
28379
28163
  const { validateWgsl } = await import("./GpuValidator-D88-VTq9.js");
28380
- const params = this.linkParams();
28381
- const linked = await link(params);
28382
- const messages = await validateWgsl(linked.dest);
28383
- const pkg = params.packageName ?? "package";
28384
- return mapGpuDiagnostics(messages, linked, this._activeFile, pkg);
28164
+ const linked = await link(this.linkParams());
28165
+ return mapGpuDiagnostics(await validateWgsl(linked.dest), linked, this._activeFile, this.pkgName());
28385
28166
  }
28386
28167
  /** Fetch missing library packages, deduplicating in-flight requests. */
28387
28168
  async fetchLibsOnDemand(names) {
@@ -28408,14 +28189,15 @@ var WgslEdit = class extends HTMLElement {
28408
28189
  }
28409
28190
  }
28410
28191
  updateLint() {
28411
- this.editorView?.dispatch({ effects: this.lintCompartment.reconfigure(this.resolveLint()) });
28192
+ this.reconfigure(this.lintCompartment, this.resolveLint());
28412
28193
  }
28413
28194
  /** Listen for compile-error/compile-success events from a lint source element. */
28414
28195
  connectLintSource(id) {
28415
28196
  const hadExternal = !!this._lintFromEl;
28416
- if (this._lintFromEl) {
28417
- this._lintFromEl.removeEventListener("compile-error", this._boundCompileError);
28418
- this._lintFromEl.removeEventListener("compile-success", this._boundCompileSuccess);
28197
+ const prev = this._lintFromEl;
28198
+ if (prev) {
28199
+ prev.removeEventListener("compile-error", this._boundCompileError);
28200
+ prev.removeEventListener("compile-success", this._boundCompileSuccess);
28419
28201
  this._lintFromEl = null;
28420
28202
  }
28421
28203
  this._externalDiagnostics = [];
@@ -28427,23 +28209,24 @@ var WgslEdit = class extends HTMLElement {
28427
28209
  }
28428
28210
  if (hadExternal !== !!this._lintFromEl) this.updateLint();
28429
28211
  }
28212
+ /** Convert external compile-error locations into CodeMirror diagnostics for the active file. */
28430
28213
  onCompileError(e) {
28431
28214
  const detail = e.detail;
28432
28215
  if (!this.editorView || detail.source === "wesl") return;
28433
28216
  const doc = this.editorView.state.doc;
28434
- const pkg = this._packageName ?? "package";
28435
- const activeModule = fileToModulePath(this._activeFile, pkg, false);
28436
- this._externalDiagnostics = detail.locations.filter((loc) => {
28437
- if (!loc.file) return true;
28438
- return fileToModulePath(loc.file, pkg, false) === activeModule;
28439
- }).map((loc) => {
28217
+ const pkg = this.pkgName();
28218
+ const activeModule = this.activeModulePath();
28219
+ const inActiveFile = (loc) => !loc.file || fileToModulePath(loc.file, pkg, false) === activeModule;
28220
+ this._externalDiagnostics = detail.locations.filter(inActiveFile).map((loc) => {
28440
28221
  const line = doc.line(Math.max(1, Math.min(loc.line, doc.lines)));
28441
28222
  const from = Math.min(line.from + (loc.column ?? 0), doc.length);
28223
+ const to = Math.min(from + (loc.length ?? 1), doc.length);
28224
+ const { severity, message } = loc;
28442
28225
  return {
28443
28226
  from,
28444
- to: Math.min(from + (loc.length ?? 1), doc.length),
28445
- severity: loc.severity,
28446
- message: loc.message,
28227
+ to,
28228
+ severity,
28229
+ message,
28447
28230
  source: "WebGPU"
28448
28231
  };
28449
28232
  });
@@ -28458,21 +28241,20 @@ var WgslEdit = class extends HTMLElement {
28458
28241
  return this._lineNumbers ? [] : EditorView.theme({ ".cm-gutters": { display: "none" } });
28459
28242
  }
28460
28243
  updateLineNumbers() {
28461
- const ext = this.resolveLineNumbers();
28462
- this.editorView?.dispatch({ effects: this.lineNumbersCompartment.reconfigure(ext) });
28244
+ this.reconfigure(this.lineNumbersCompartment, this.resolveLineNumbers());
28463
28245
  }
28464
28246
  /** Parse script tags into _files. Supports single or multi-file via data-name. */
28465
28247
  parseInlineContent() {
28466
28248
  const scripts = Array.from(this.querySelectorAll("script[type=\"text/wgsl\"], script[type=\"text/wesl\"]"));
28467
28249
  if (scripts.length === 0) {
28468
28250
  const content = this.textContent?.trim() ?? "";
28469
- if (content) this._files.set("main.wesl", { doc: Text.of(content.split("\n")) });
28251
+ if (content) this._files.set("main.wesl", { doc: toDoc(content) });
28470
28252
  return;
28471
28253
  }
28472
28254
  for (const script of scripts) {
28473
28255
  const name = script.getAttribute("data-name") || "main.wesl";
28474
28256
  const content = script.textContent?.trim() ?? "";
28475
- this._files.set(name, { doc: Text.of(content.split("\n")) });
28257
+ this._files.set(name, { doc: toDoc(content) });
28476
28258
  }
28477
28259
  }
28478
28260
  renderTabs() {
@@ -28517,28 +28299,27 @@ var WgslEdit = class extends HTMLElement {
28517
28299
  });
28518
28300
  return btn;
28519
28301
  }
28302
+ /** Replace the tab name span with an editable input; commit on Enter/blur, cancel on Escape. */
28520
28303
  startRenameTab(tab, nameSpan, oldName) {
28521
28304
  const input = document.createElement("input");
28522
28305
  input.className = "tab-rename";
28523
28306
  input.value = oldName;
28524
28307
  input.size = Math.max(oldName.length, 8);
28308
+ const cancelRename = () => {
28309
+ nameSpan.style.display = "";
28310
+ input.remove();
28311
+ };
28525
28312
  const finishRename = () => {
28526
28313
  const newName = input.value.trim() || oldName;
28527
28314
  if (newName !== oldName && !this._files.has(newName)) this.renameFile(oldName, newName);
28528
- else {
28529
- nameSpan.style.display = "";
28530
- input.remove();
28531
- }
28315
+ else cancelRename();
28532
28316
  };
28533
28317
  input.addEventListener("keydown", (e) => {
28534
28318
  if (e.key === "Enter") {
28535
28319
  e.preventDefault();
28536
28320
  finishRename();
28537
28321
  }
28538
- if (e.key === "Escape") {
28539
- nameSpan.style.display = "";
28540
- input.remove();
28541
- }
28322
+ if (e.key === "Escape") cancelRename();
28542
28323
  });
28543
28324
  input.addEventListener("blur", finishRename);
28544
28325
  input.addEventListener("input", () => {
@@ -28566,6 +28347,7 @@ var WgslEdit = class extends HTMLElement {
28566
28347
  if (src) this.loadFromUrl(src);
28567
28348
  }
28568
28349
  };
28350
+ /** Build a CodeMirror highlight style from a WESL color palette. */
28569
28351
  function weslColors(c) {
28570
28352
  return syntaxHighlighting(HighlightStyle.define([
28571
28353
  {
@@ -28633,24 +28415,7 @@ function weslColors(c) {
28633
28415
  fontStyle: "normal"
28634
28416
  } }));
28635
28417
  }
28636
- /** Map GPU validation messages back to source positions via the source map. */
28637
- function mapGpuDiagnostics(messages, linked, activeFile, pkg) {
28638
- const { sourceMap } = linked;
28639
- const active = fileToModulePath(activeFile, pkg, false);
28640
- return messages.flatMap((msg) => {
28641
- const srcPos = sourceMap.destToSrc(msg.offset);
28642
- if ((srcPos.src.path ? fileToModulePath(srcPos.src.path, pkg, false) : null) !== active) return [];
28643
- const endPos = sourceMap.destToSrc(msg.offset + msg.length);
28644
- const from = srcPos.position;
28645
- return {
28646
- from,
28647
- to: endPos.position > from ? endPos.position : from + 1,
28648
- severity: msg.severity,
28649
- message: msg.message,
28650
- source: "WebGPU"
28651
- };
28652
- });
28653
- }
28418
+ /** Lazily build and cache the shared component stylesheet. */
28654
28419
  function getStyles() {
28655
28420
  if (!cachedStyleSheet) {
28656
28421
  cachedStyleSheet = new CSSStyleSheet();
@@ -28658,7 +28423,8 @@ function getStyles() {
28658
28423
  }
28659
28424
  return cachedStyleSheet;
28660
28425
  }
28661
- /** Absorb instance properties set before custom element upgrade. */
28426
+ /** Absorb instance properties set before custom element upgrade.
28427
+ * Duplicated in WgslPlay.ts. Later, extract to a shared package. */
28662
28428
  function upgradeProperty(el, prop) {
28663
28429
  if (Object.hasOwn(el, prop)) {
28664
28430
  const value = el[prop];
@@ -28666,11 +28432,36 @@ function upgradeProperty(el, prop) {
28666
28432
  el[prop] = value;
28667
28433
  }
28668
28434
  }
28435
+ /** Build a CodeMirror Text doc from a string. */
28436
+ function toDoc(s) {
28437
+ return Text.of(s.split("\n"));
28438
+ }
28669
28439
  /** Convert a module path or file path to a tab name: "package::main" -> "main", "main.wesl" -> "main.wesl" */
28670
28440
  function toTabName(key) {
28671
28441
  if (key.includes("::")) return key.replace(/^[^:]+::/, "").replaceAll("::", "/");
28672
28442
  return key.replace(/^\.\//, "");
28673
28443
  }
28444
+ /** Map GPU validation messages back to source positions via the source map. */
28445
+ function mapGpuDiagnostics(messages, linked, activeFile, pkg) {
28446
+ const { sourceMap } = linked;
28447
+ const active = fileToModulePath(activeFile, pkg, false);
28448
+ return messages.flatMap((msg) => {
28449
+ const srcPos = sourceMap.destToSrc(msg.offset);
28450
+ const path = srcPos.src.path;
28451
+ if ((path ? fileToModulePath(path, pkg, false) : null) !== active) return [];
28452
+ const endPos = sourceMap.destToSrc(msg.offset + msg.length);
28453
+ const from = srcPos.position;
28454
+ const to = endPos.position > from ? endPos.position : from + 1;
28455
+ const { severity, message } = msg;
28456
+ return {
28457
+ from,
28458
+ to,
28459
+ severity,
28460
+ message,
28461
+ source: "WebGPU"
28462
+ };
28463
+ });
28464
+ }
28674
28465
  //#endregion
28675
28466
  //#region src/index.ts
28676
28467
  if (!customElements.get("wgsl-edit")) customElements.define("wgsl-edit", WgslEdit);