forge-select 0.7.4 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -205,6 +205,17 @@ Forge Select is vanilla TypeScript/JavaScript, so it can be mounted inside any f
205
205
 
206
206
  Forge Select is designed as a drop-in-concept replacement for Select2: no jQuery dependency, native accessibility, and a smaller API surface. A full option/event/method mapping table and a step-by-step migration checklist are available in [`docs/migration-from-select2.md`](./docs/migration-from-select2.md).
207
207
 
208
+ ## Bundle size
209
+
210
+ Forge Select ships as a single class, so **the whole feature set is in the bundle whether or not you use it** — tree select, AJAX, tags, sortable tags and virtualization cannot be tree-shaken away. Measured with esbuild (minified + gzipped):
211
+
212
+ | Usage | JS |
213
+ | --------------------------------------------------------------- | ------- |
214
+ | Minimal — `new ForgeSelect(el, { placeholder })` | 13.3 KB |
215
+ | Every feature — tree, AJAX, tags, sortable, measured rows, etc. | 13.4 KB |
216
+
217
+ Plus 1.7 KB gzipped for `forge-select/styles.css`. Budget ~15 KB gzipped total and the number won't move as you adopt more of the API. Splitting the core into opt-in subpath entrypoints is under consideration for a future major version; it is not planned as a patch, since it would change the public import surface.
218
+
208
219
  ## Benchmarks
209
220
 
210
221
  Run `npm run bench` for a reproducible JSON baseline covering bundle size, initialization, 10,000-option search latency, and virtual-scroll performance. The methodology and result fields are documented in [`docs/benchmarks.md`](./docs/benchmarks.md).
package/dist/index.cjs CHANGED
@@ -334,7 +334,12 @@ var uidCounter = 0;
334
334
  var ForgeSelect = class {
335
335
  constructor(target, options = {}) {
336
336
  this.optionByValue = /* @__PURE__ */ new Map();
337
- this.optionByLabel = /* @__PURE__ */ new Map();
337
+ // Built on demand: only the allowCreate paths read it, and filling it eagerly
338
+ // normalizes every label (NFD + regex) at construction for the majority of
339
+ // selects that never create tags. rebuildOptionIndexes() nulls it, so every
340
+ // existing invalidation point — including an accentInsensitive change —
341
+ // already covers it.
342
+ this.optionByLabel = null;
338
343
  this.selected = [];
339
344
  this.selectedOptions = /* @__PURE__ */ new Map();
340
345
  this.suppressNextTagClick = false;
@@ -507,6 +512,7 @@ var ForgeSelect = class {
507
512
  if (this.opts.ajax && (this.opts.ajax.loadOnOpen ?? true) && !this.remoteLoaded) {
508
513
  this.scheduleRemoteLoad(this.query, 0);
509
514
  }
515
+ if (this.opts.allowCreate) this.labelIndex();
510
516
  this.renderList();
511
517
  this.positionDropdown();
512
518
  window.addEventListener("resize", this.onWindowResize);
@@ -1265,18 +1271,29 @@ var ForgeSelect = class {
1265
1271
  findOption(value) {
1266
1272
  return this.optionByValue.get(value);
1267
1273
  }
1274
+ /** Fills the label index on first use; see the field for why it is deferred. */
1275
+ labelIndex() {
1276
+ if (this.optionByLabel) return this.optionByLabel;
1277
+ const index = /* @__PURE__ */ new Map();
1278
+ const visit = (option) => {
1279
+ const label = normalizeSearchText(option.label, this.opts.accentInsensitive);
1280
+ if (!index.has(label)) index.set(label, option);
1281
+ option.children?.forEach(visit);
1282
+ };
1283
+ for (const item of this.data) (isGroup(item) ? item.options : [item]).forEach(visit);
1284
+ this.optionByLabel = index;
1285
+ return index;
1286
+ }
1268
1287
  findOptionByLabel(label) {
1269
- return this.optionByLabel.get(normalizeSearchText(label, this.opts.accentInsensitive));
1288
+ return this.labelIndex().get(normalizeSearchText(label, this.opts.accentInsensitive));
1270
1289
  }
1271
1290
  rebuildOptionIndexes() {
1272
1291
  this.optionByValue.clear();
1273
- this.optionByLabel.clear();
1292
+ this.optionByLabel = null;
1274
1293
  const duplicates = /* @__PURE__ */ new Set();
1275
1294
  const visit = (option) => {
1276
1295
  if (this.optionByValue.has(option.value)) duplicates.add(option.value);
1277
1296
  else this.optionByValue.set(option.value, option);
1278
- const label = normalizeSearchText(option.label, this.opts.accentInsensitive);
1279
- if (!this.optionByLabel.has(label)) this.optionByLabel.set(label, option);
1280
1297
  option.children?.forEach(visit);
1281
1298
  };
1282
1299
  for (const item of this.data) (isGroup(item) ? item.options : [item]).forEach(visit);