@twobirds/microcomponents 0.10.1 → 0.10.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.
Files changed (39) hide show
  1. package/dist/MC.min.js +1 -1
  2. package/dist/autoload.min.js +1 -1
  3. package/dist/cli/index.js +18 -22
  4. package/dist/cli/inliner.d.ts +8 -5
  5. package/dist/cli/inliner.d.ts.map +1 -1
  6. package/dist/core/MC.d.ts +98 -50
  7. package/dist/core/MC.d.ts.map +1 -1
  8. package/dist/core/elements.js +134 -76
  9. package/dist/core/index.d.ts +5 -4
  10. package/dist/core/index.d.ts.map +1 -1
  11. package/dist/docs/api-external.d.ts +101 -0
  12. package/dist/docs/api-external.d.ts.map +1 -0
  13. package/dist/docs/cli.d.ts +115 -0
  14. package/dist/docs/cli.d.ts.map +1 -0
  15. package/dist/docs/concepts.d.ts +42 -0
  16. package/dist/docs/concepts.d.ts.map +1 -0
  17. package/dist/docs/installation.d.ts +74 -0
  18. package/dist/docs/installation.d.ts.map +1 -0
  19. package/dist/docs/mcp.d.ts +45 -0
  20. package/dist/docs/mcp.d.ts.map +1 -0
  21. package/dist/elements.min.js +1 -1
  22. package/dist/formValues.min.js +1 -1
  23. package/dist/mcp/api.json +325 -29
  24. package/dist/mcp/concepts/event-system.md +1 -1
  25. package/dist/mcp/index.js +13 -6
  26. package/dist/microcomponents.js +145 -82
  27. package/dist/microcomponents.min.js +1 -1
  28. package/dist/observables.min.js +1 -1
  29. package/dist/state/formValues.d.ts +3 -3
  30. package/dist/state/formValues.js +139 -81
  31. package/dist/state/index.d.ts +3 -0
  32. package/dist/state/index.d.ts.map +1 -1
  33. package/dist/state/observables.d.ts +2 -2
  34. package/dist/state/observables.js +136 -78
  35. package/dist/types/index.d.ts +1 -0
  36. package/dist/types/index.d.ts.map +1 -1
  37. package/dist/utils/helpers.d.ts +12 -0
  38. package/dist/utils/helpers.d.ts.map +1 -1
  39. package/package.json +3 -2
@@ -399,6 +399,20 @@ function deepEqual(a, b) {
399
399
  return true;
400
400
  }
401
401
  /**
402
+ * Converts a PascalCase string to kebab-case.
403
+ * Example: 'MyComponentName' -> 'my-component-name'
404
+ * @param str - The PascalCase string to convert.
405
+ * @returns The converted kebab-case string.
406
+ *
407
+ * @example
408
+ * ```ts
409
+ * console.log(pascalToKebab('MyElement'));
410
+ * ```
411
+ */
412
+ function pascalToKebab(str) {
413
+ return str.replace(/[A-Z]/g, (m, i) => (i ? "-" : "") + m.toLowerCase());
414
+ }
415
+ /**
402
416
  * Copies all getter and setter properties from a source object to a target object.
403
417
  * Does not copy methods or regular data properties—only accessor properties (get/set).
404
418
  * @hidden
@@ -641,13 +655,15 @@ function remove(target, key) {
641
655
  if (!Object.keys(_mc).length && target instanceof HTMLElement && target.hasAttribute("data-mc")) target.removeAttribute("data-mc");
642
656
  }
643
657
  /**
644
- Runs the "Init" lifecycle event on a DOM microcomponent instance asynchronously.
658
+ Runs the "Init" lifecycle event on a DOM microcomponent instance.
645
659
 
646
660
  This function schedules the `trigger('Init')` call in a microtask, ensuring
647
661
  that all synchronous initialization is complete before the lifecycle hook
648
- executes. Only `DC` instances have an Init lifecycle — the `McBase`
649
- constructor calls this exclusively behind an `instanceof DC` check, so the
650
- signature encodes that invariant instead of re-checking it at runtime.
662
+ executes. The {@link DC} constructor calls it once the declared assets are in
663
+ place — synchronously in a bundled build, after the fetch resolves in a
664
+ dev/autoload build — so `oneInit()` always sees the loaded assets. Only `DC`
665
+ instances have an Init lifecycle — the `DC` constructor calls this exclusively,
666
+ so the signature encodes that invariant instead of re-checking it at runtime.
651
667
 
652
668
  @param element - The DOM microcomponent instance on which to trigger the Init event.
653
669
  */
@@ -715,16 +731,18 @@ function findMcAncestor(from, key) {
715
731
  The base class for all microcomponents — shared root of both {@link MC} and {@link DC}.
716
732
 
717
733
  Holds a `WeakRef` to the target (a DOM element for `DC`, or any plain object for `MC`)
718
- to avoid circular references. For `DC` subclasses it also bootstraps two automatic
719
- behaviours:
734
+ to avoid circular references. It starts loading any `static assets` for `MC` and `DC`
735
+ alike (shared per class through `_assetsPromise`), read through the {@link McBase.assets}
736
+ getter. For `DC` subclasses it bootstraps one automatic behaviour:
720
737
 
721
738
  1. **Event listener auto-attachment** — methods whose names start with `on` or `one`
722
739
  (e.g. `onClick`, `oneInit`) are wired to the target element via {@link on} / {@link one}.
723
- 2. **"Init" lifecycle event** — dispatched asynchronously in a microtask so that all
724
- synchronous constructor work finishes first.
725
740
 
726
- Both mechanisms run only when the instance is a `DC` (checked with `instanceof DC`).
727
- Plain `MC` instances skip them entirely, keeping server-side usage lightweight.
741
+ The **"Init" lifecycle event** and the DOM attach of auto-attached assets are dispatched
742
+ by the {@link DC} constructor — after the declared assets are in place — so that
743
+ `oneInit()` runs once the instance is fully constructed and its assets are loaded. Plain
744
+ `MC` instances fetch their assets server-side with no DOM attach and no `Init` lifecycle,
745
+ keeping server-side usage lightweight.
728
746
 
729
747
  ### Plugin extension points
730
748
 
@@ -777,20 +795,45 @@ runs once after the constructor.
777
795
  var McBase = class {
778
796
  _target;
779
797
  /**
798
+ * Loaded asset cache, keyed by camelCase descriptor (`'targetHtml'` → `.target.html`).
799
+ * Inlined by the bundler in a bundled build, filled by `loadAssets()` in dev/autoload.
800
+ * Read through the {@link McBase.assets} getter.
801
+ */
802
+ static _assets = {};
803
+ /**
804
+ * Declares companion files for auto-loading by convention (e.g. `['targetHtml', 'json']`).
805
+ * Asset names are camelCase and translated into file extensions: `'targetHtml'` → `.target.html`.
806
+ */
807
+ static assets = [];
808
+ /**
809
+ * In-flight asset request, cached per class so that instances constructed while
810
+ * the first fetch is still pending (e.g. in a loop) share a single request
811
+ * instead of each triggering their own. Resolves to the freshly loaded assets.
812
+ */
813
+ static _assetsPromise;
814
+ /**
780
815
  The constructor for the base `McBase` class.
781
816
 
782
- Initializes a weak reference to the target and triggers auto-attachment
783
- of listeners and initialization lifecycle events.
817
+ Initializes a weak reference to the target, auto-attaches listeners for `DC`
818
+ subclasses, and starts loading any `static assets` for `MC` and `DC` alike —
819
+ a server-side `MC` fetches them with no DOM, a `DC` gets them attached by its
820
+ own constructor. The load is shared per class through `_assetsPromise`.
784
821
 
785
822
  @param target - The object or element this microcomponent represents.
786
823
  */
787
824
  constructor(target) {
788
825
  this._target = new WeakRef(target || {});
789
- if (this instanceof DC && this.constructor !== DC) {
790
- autoAttachListeners(this);
791
- const tag = this.constructor.tag;
792
- if (tag === void 0 || tag.toUpperCase() === this.target.tagName) init(this);
793
- }
826
+ const ctor = this.constructor;
827
+ if (this instanceof DC && this.constructor !== DC) autoAttachListeners(this);
828
+ const remaining = (ctor.assets ?? []).filter((key) => !ctor._assets[key]);
829
+ if (remaining.length && !ctor._assetsPromise) ctor._assetsPromise = loadAssets(ctor._originalTag || (this instanceof DC ? this.target.tagName.toLowerCase() : pascalToKebab(ctor.name)), remaining).then((res) => {
830
+ const loadedAssets = {};
831
+ remaining.forEach((key, i) => {
832
+ loadedAssets[key] = res[i];
833
+ });
834
+ ctor._assets = Object.assign({}, ctor._assets, loadedAssets);
835
+ return loadedAssets;
836
+ });
794
837
  }
795
838
  /**
796
839
  Returns the current target object (DOM node or other data) referenced by this microcomponent.
@@ -808,6 +851,21 @@ var McBase = class {
808
851
  get target() {
809
852
  return this._target.deref();
810
853
  }
854
+ /**
855
+ The loaded assets for this component instance.
856
+
857
+ `static assets` are loaded before `oneInit()` fires on a `DC`, so this getter is
858
+ ready inside the `oneInit()` callback. A server-side `MC` fills it from `loadAssets()`;
859
+ await `MC._assetsPromise` before reading it. It returns the merged asset record keyed
860
+ by descriptor: `css` / `targetCss` / `shadowCss` / `shadowHtml` / `targetHtml` arrive
861
+ as strings, `*Json` entries arrive parsed, and a plain `html` template is returned as a
862
+ string for `parse()`. An empty object when no assets are declared.
863
+
864
+ @returns A record mapping asset keys to their content.
865
+ */
866
+ get assets() {
867
+ return this.constructor._assets || {};
868
+ }
811
869
  };
812
870
  /**
813
871
  @class DC
@@ -865,14 +923,26 @@ and translated into file extensions: `'targetHtml'` → `.target.html`, `'json'`
865
923
  The path is derived from the custom element tag segments, and the basename is the
866
924
  last segment. For `<cart-container-checkout>`, assets load from `/cart/container/checkout.*`.
867
925
 
868
- When assets finish loading, an `'assets'` event fires so that `oneAssets(ev)` can
869
- process them. Everything ending in `Json` arrives parsed; all other files arrive as text.
926
+ **Assets are loaded before `oneInit()` fires.** The constructor applies them to the
927
+ target, then dispatches the `Init` lifecycle event — synchronously in a bundled build
928
+ (assets inlined), after the fetch resolves in a dev/autoload build. Inside `oneInit()`
929
+ the loaded assets are read through the {@link DC.assets} getter. Everything ending in
930
+ `Json` arrives parsed; all other files arrive as text.
931
+
932
+ **Auto-attached assets**: `.css`, `.target.css`, `.shadow.css`, `.target.html` and
933
+ `.shadow.html` are attached automatically by `processAssets()` — CSS is injected into
934
+ the document or shadow scope, HTML is set as `target.innerHTML` / `shadowRoot.innerHTML`,
935
+ and native event handlers in the template (`<button click="this|go">`) are wired.
936
+
937
+ **Plain `.html` is NOT auto-attached.** It is a template held in {@link DC.assets} for
938
+ components created programmatically with a `data` parameter: in `oneInit()` you compile
939
+ the target markup from template + data with `parse()`.
870
940
 
871
941
  **One-time global styles**: Assets named `'css'` and `'targetCss'` inject CSS into the
872
942
  global document scope via `injectCSS(document, ...)`. Because these are idempotent
873
943
  global side effects, they are processed only once — after the first instance loads them,
874
- the entries are removed from both `static assets` and `static _assets`. Subsequent
875
- instances skip loading and re-processing them entirely.
944
+ the entries are dropped from `static _assets`. Subsequent instances skip re-processing
945
+ them entirely.
876
946
 
877
947
  **CSS file roles**:
878
948
 
@@ -883,7 +953,9 @@ scope once, like `targetCss`.
883
953
  component's own shadow root, so each instance gets its own copy.
884
954
 
885
955
  Every declared asset — CSS, HTML, JSON — is inlined into `static _assets` at build time by
886
- the load-assets plugin, so a bundled build performs no fetches at all.
956
+ the bundler, which replaces the `static assets` declaration with `static _assets` (the
957
+ `static assets` line is deleted from the bundled `.js`) and collapses each inlined
958
+ `.[shadow/target].css` / `.html` to a single line, so a bundled build performs no fetches.
887
959
 
888
960
  In a bundled build the plain `.css` files are additionally concatenated into a single
889
961
  `bundle.css` by the bundling script. That file is a convenience listing of the available
@@ -895,14 +967,14 @@ The runtime decides whether an asset still needs fetching with `!ctor._assets[ke
895
967
  inlined entry must never be an empty string: that would trigger a re-fetch of a file that
896
968
  does not exist in a bundled build.
897
969
 
898
- @example static assets with autoload
970
+ @example static assets read in oneInit
899
971
 
900
972
  ```ts
901
973
  class CartCheckout extends DC {
902
974
  static assets = ['targetHtml', 'targetCss', 'configJson'];
903
975
 
904
- oneAssets(ev) {
905
- const { configJson } = ev.detail.data;
976
+ oneInit() {
977
+ const { configJson } = this.assets;
906
978
  console.log(configJson);
907
979
  }
908
980
  }
@@ -911,6 +983,26 @@ defineCE('cart-checkout', CartCheckout);
911
983
 
912
984
  `configJson` arrives already parsed into an object.
913
985
 
986
+ @example plain .html template compiled with data
987
+
988
+ ```ts
989
+ class UserCard extends DC {
990
+ static assets = ['html', 'targetCss'];
991
+
992
+ constructor(target: HTMLElement, data: { name: string }) {
993
+ super(target);
994
+ this.data = data;
995
+ }
996
+
997
+ oneInit() {
998
+ this.target.innerHTML = parse(this.assets.html, this.data);
999
+ }
1000
+ }
1001
+ defineCE('user-card', UserCard);
1002
+ ```
1003
+
1004
+ `user-card.html` holds the `{name}` template; `parse()` fills it from `data`.
1005
+
914
1006
  ### `static attributes?: object`
915
1007
 
916
1008
  Sets HTML attributes on every instance automatically.
@@ -964,8 +1056,7 @@ internal events that can be caught with handler methods:
964
1056
  | `onDisconnected()` | Element was removed from the DOM (`disconnectedCallback`) |
965
1057
  | `onAdopted()` | Element was moved to a new document (`adoptedCallback`) |
966
1058
  | `onAttributeChanged()` | An observed attribute changed (`attributeChangedCallback`) |
967
- | `oneInit()` | Constructor finished; all synchronous setup complete (microtask) |
968
- | `oneAssets()` | Constructor finished; all assets loaded and available; ev contains assets object |
1059
+ | `oneInit()` | Constructor finished and declared assets loaded; read them via `this.assets` |
969
1060
 
970
1061
  @example Event handlers in action
971
1062
 
@@ -1080,22 +1171,6 @@ var DC = class DC extends McBase {
1080
1171
  */
1081
1172
  static tag;
1082
1173
  /**
1083
- * Result container for assets (e.g., `['targetHtml', 'json']`).
1084
- * Asset names (keys) are camelCase and translated into file extensions: `'targetHtml'` → `.target.html`.
1085
- */
1086
- static _assets = {};
1087
- /**
1088
- * Declares companion files for auto-loading by convention (e.g., `['targetHtml', 'json']`).
1089
- * Asset names are camelCase and translated into file extensions: `'targetHtml'` → `.target.html`.
1090
- */
1091
- static assets = [];
1092
- /**
1093
- * In-flight asset request, cached per class so that instances constructed while
1094
- * the first fetch is still pending (e.g. in a loop) share a single request
1095
- * instead of each triggering their own. Resolves to the freshly loaded assets.
1096
- */
1097
- static _assetsPromise;
1098
- /**
1099
1174
  * Sets HTML attributes on every instance automatically.
1100
1175
  * Example: `{ role: 'listitem', 'aria-label': 'nav' }`.
1101
1176
  */
@@ -1107,8 +1182,10 @@ var DC = class DC extends McBase {
1107
1182
  * 1. Calls {@link McBase} to establish the target reference and auto-attach listeners.
1108
1183
  * 2. If `static tag` is set and differs from the current element, replaces the target
1109
1184
  * with a new built-in element (or removes it if `tag === ''`).
1110
- * 3. If `static assets` has entries, initiates asynchronous loading, fires an `'assets'` event when complete handled by `oneAssets(ev)`.
1111
- * 4. Applies any `static attributes` to the target element.
1185
+ * 3. Applies any `static attributes` to the target element.
1186
+ * 4. Loads `static assets` (inlined in a bundled build, fetched in dev/autoload),
1187
+ * applies the auto-attached ones to the target, then fires the `Init` lifecycle
1188
+ * event — so `oneInit()` runs with the assets loaded and readable via {@link DC.assets}.
1112
1189
  *
1113
1190
  * @param target - The host DOM element this component operates on.
1114
1191
  */
@@ -1131,43 +1208,24 @@ var DC = class DC extends McBase {
1131
1208
  if (ctor.attributes) Object.keys(ctor.attributes).forEach((key) => {
1132
1209
  this.target.setAttribute(key, attributes[key]);
1133
1210
  });
1134
- const that = this;
1135
- const remainingAssets = ctor.assets.filter((key) => !ctor._assets[key]);
1136
- if (Object.keys(ctor._assets).length) processAssets(this, ctor._assets);
1137
- if (remainingAssets.length) {
1138
- if (!ctor._assetsPromise) ctor._assetsPromise = loadAssets(ctor._originalTag || tagName, remainingAssets).then((res) => {
1139
- const loadedAssets = {};
1140
- remainingAssets.forEach((key, i) => {
1141
- loadedAssets[key] = res[i];
1142
- });
1143
- ctor._assets = Object.assign({}, ctor._assets, loadedAssets);
1144
- for (const onceKey of ["css", "targetCss"]) if (loadedAssets[onceKey] !== void 0) delete ctor._assets[onceKey];
1145
- ctor.assets = ctor.assets.filter((key) => key !== "css" && key !== "targetCss");
1146
- return loadedAssets;
1147
- });
1148
- ctor._assetsPromise.then((loadedAssets) => {
1149
- processAssets(this, loadedAssets);
1150
- that.trigger("assets", ctor._assets);
1151
- });
1152
- } else if ([...Object.keys(ctor._assets)].length) queueMicrotask(() => that.trigger("assets", ctor._assets));
1211
+ if (Object.keys(ctor._assets).length) {
1212
+ processAssets(this, ctor._assets);
1213
+ for (const onceKey of ["css", "targetCss"]) delete ctor._assets[onceKey];
1214
+ ctor.assets = ctor.assets.filter((key) => key !== "css" && key !== "targetCss");
1215
+ }
1216
+ if (ctor._assetsPromise) ctor._assetsPromise.then((loadedAssets) => {
1217
+ processAssets(this, loadedAssets);
1218
+ for (const onceKey of ["css", "targetCss"]) delete ctor._assets[onceKey];
1219
+ ctor.assets = ctor.assets.filter((key) => key !== "css" && key !== "targetCss");
1220
+ init(this);
1221
+ });
1222
+ else init(this);
1153
1223
  if (ctor.tag === "") {
1154
1224
  this.target.remove();
1155
1225
  return;
1156
1226
  }
1157
1227
  }
1158
1228
  /**
1159
- Returns the loaded assets for this component instance.
1160
-
1161
- Assets are loaded asynchronously when `static assets` is defined on the class.
1162
- This method returns the resolved asset map keyed by filename, or `undefined`
1163
- if assets have not yet been loaded.
1164
-
1165
- @returns A record mapping asset keys to their content strings, or undefined.
1166
- */
1167
- getAssets() {
1168
- return this.constructor._assets || {};
1169
- }
1170
- /**
1171
1229
  Static helper to attach a microcomponent instance to a DOM element.
1172
1230
 
1173
1231
  Wrapper around the `add()` utility function, providing a class-level API
@@ -1524,7 +1582,7 @@ observePlugin(DC);
1524
1582
  class Counter extends DC {
1525
1583
  data = { clicks: 1 };
1526
1584
 
1527
- oneAssets() {
1585
+ oneInit() {
1528
1586
  this
1529
1587
  .subscribe('data', (v) => console.log('data →', v))
1530
1588
  .bind('data', this.target);
@@ -1907,7 +1965,7 @@ subsequent calls are no-ops.
1907
1965
  class Counter extends DC {
1908
1966
  data = { clicks: 1 };
1909
1967
 
1910
- oneAssets() {
1968
+ oneInit() {
1911
1969
  this
1912
1970
  .subscribe('data', (v) => console.log('data →', v))
1913
1971
  .bind('data', this.target);
@@ -2003,7 +2061,7 @@ formValuePlugin(DC);
2003
2061
  class CheckoutForm extends DC {
2004
2062
  static assets = ['targetHtml'];
2005
2063
 
2006
- oneAssets() {
2064
+ oneInit() {
2007
2065
  this
2008
2066
  .formValues(this.target, 'formData')
2009
2067
  .subscribe('formData', (data) => console.log(data))
@@ -2291,7 +2349,7 @@ import { DC, defineCE, formValues } from '@twobirds/microcomponents';
2291
2349
  class MyForm extends DC {
2292
2350
  static assets = ['targetHtml'];
2293
2351
 
2294
- oneAssets() {
2352
+ oneInit() {
2295
2353
  formValues(this, 'formData', this.target);
2296
2354
  this.formData.subscribe((data) => console.log('form changed:', data));
2297
2355
  }
@@ -2336,7 +2394,7 @@ formValuePlugin(DC);
2336
2394
  class CheckoutForm extends DC {
2337
2395
  static assets = ['targetHtml'];
2338
2396
 
2339
- oneAssets() {
2397
+ oneInit() {
2340
2398
  this
2341
2399
  .formValues(this.target, 'formData')
2342
2400
  .subscribe('formData', (data) => console.log(data))
@@ -1,7 +1,10 @@
1
1
  /**
2
2
  @module State
3
3
  */
4
+ /** @group State */
4
5
  export { observe, observePlugin, isAlreadyObserved } from './observables.js';
6
+ /** @group State */
5
7
  export type { Observable, Callback, Observed, ObservedKey, Unobserved } from './observables.js';
8
+ /** @group State */
6
9
  export { formValues, formValuePlugin } from './formValues.js';
7
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/state/index.ts"],"names":[],"mappings":"AAAA;;EAEE;AACF,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAChG,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/state/index.ts"],"names":[],"mappings":"AAAA;;EAEE;AACF,mBAAmB;AACnB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,mBAAmB;AACnB,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAChG,mBAAmB;AACnB,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
@@ -28,7 +28,7 @@ observePlugin(DC);
28
28
  class Counter extends DC {
29
29
  data = { clicks: 1 };
30
30
 
31
- oneAssets() {
31
+ oneInit() {
32
32
  this
33
33
  .subscribe('data', (v) => console.log('data →', v))
34
34
  .bind('data', this.target);
@@ -220,7 +220,7 @@ subsequent calls are no-ops.
220
220
  class Counter extends DC {
221
221
  data = { clicks: 1 };
222
222
 
223
- oneAssets() {
223
+ oneInit() {
224
224
  this
225
225
  .subscribe('data', (v) => console.log('data →', v))
226
226
  .bind('data', this.target);