@studiometa/js-toolkit 3.4.3 → 3.6.0-beta.0

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 (104) hide show
  1. package/Base/Base.d.ts +21 -1
  2. package/Base/Base.js +47 -5
  3. package/Base/Base.js.map +2 -2
  4. package/Base/managers/AbstractManager.d.ts +2 -2
  5. package/Base/managers/EventsManager.js +1 -1
  6. package/Base/managers/EventsManager.js.map +2 -2
  7. package/Base/utils.d.ts +1 -1
  8. package/Base/utils.js +1 -2
  9. package/Base/utils.js.map +2 -2
  10. package/decorators/withBreakpointManager.js +1 -1
  11. package/decorators/withBreakpointManager.js.map +2 -2
  12. package/helpers/createApp.js +8 -17
  13. package/helpers/createApp.js.map +2 -2
  14. package/helpers/defineFeatures.d.ts +7 -0
  15. package/helpers/defineFeatures.js +21 -0
  16. package/helpers/defineFeatures.js.map +7 -0
  17. package/helpers/getDirectChildren.js +4 -3
  18. package/helpers/getDirectChildren.js.map +2 -2
  19. package/helpers/index.d.ts +3 -0
  20. package/helpers/index.js +12 -0
  21. package/helpers/index.js.map +2 -2
  22. package/helpers/logTree.d.ts +17 -0
  23. package/helpers/logTree.js +59 -0
  24. package/helpers/logTree.js.map +7 -0
  25. package/helpers/queryComponent.d.ts +28 -0
  26. package/helpers/queryComponent.js +66 -0
  27. package/helpers/queryComponent.js.map +7 -0
  28. package/index.d.ts +1 -1
  29. package/index.js +1 -1
  30. package/index.js.map +1 -1
  31. package/package.json +1 -1
  32. package/services/AbstractService.d.ts +1 -1
  33. package/services/AbstractService.js +1 -1
  34. package/services/AbstractService.js.map +2 -2
  35. package/services/DragService.d.ts +5 -5
  36. package/services/RafService.d.ts +1 -1
  37. package/services/RafService.js +13 -7
  38. package/services/RafService.js.map +2 -2
  39. package/services/ScrollService.d.ts +21 -3
  40. package/services/ScrollService.js +55 -7
  41. package/services/ScrollService.js.map +2 -2
  42. package/utils/collide/boundingRectToCircle.d.ts +2 -2
  43. package/utils/css/animate.js +166 -96
  44. package/utils/css/animate.js.map +2 -2
  45. package/utils/css/transform.js +12 -13
  46. package/utils/css/transform.js.map +2 -2
  47. package/utils/index.d.ts +1 -0
  48. package/utils/index.js +1 -0
  49. package/utils/index.js.map +2 -2
  50. package/utils/math/smoothTo.d.ts +24 -6
  51. package/utils/memoize.d.ts +1 -1
  52. package/utils/object/getAllProperties.js +4 -2
  53. package/utils/object/getAllProperties.js.map +2 -2
  54. package/utils/scheduler.d.ts +1 -1
  55. package/utils/storage/createLocalStorageProvider.d.ts +2 -0
  56. package/utils/storage/createLocalStorageProvider.js +32 -0
  57. package/utils/storage/createLocalStorageProvider.js.map +7 -0
  58. package/utils/storage/createMemoryStorageProvider.d.ts +2 -0
  59. package/utils/storage/createMemoryStorageProvider.js +27 -0
  60. package/utils/storage/createMemoryStorageProvider.js.map +7 -0
  61. package/utils/storage/createNoopProvider.d.ts +2 -0
  62. package/utils/storage/createNoopProvider.js +23 -0
  63. package/utils/storage/createNoopProvider.js.map +7 -0
  64. package/utils/storage/createSessionStorageProvider.d.ts +2 -0
  65. package/utils/storage/createSessionStorageProvider.js +31 -0
  66. package/utils/storage/createSessionStorageProvider.js.map +7 -0
  67. package/utils/storage/createStorage.d.ts +2 -0
  68. package/utils/storage/createStorage.js +100 -0
  69. package/utils/storage/createStorage.js.map +7 -0
  70. package/utils/storage/createUrlSearchParamsInHashProvider.d.ts +2 -0
  71. package/utils/storage/createUrlSearchParamsInHashProvider.js +57 -0
  72. package/utils/storage/createUrlSearchParamsInHashProvider.js.map +7 -0
  73. package/utils/storage/createUrlSearchParamsProvider.d.ts +2 -0
  74. package/utils/storage/createUrlSearchParamsProvider.js +41 -0
  75. package/utils/storage/createUrlSearchParamsProvider.js.map +7 -0
  76. package/utils/storage/getBrowserContext.d.ts +1 -0
  77. package/utils/storage/getBrowserContext.js +17 -0
  78. package/utils/storage/getBrowserContext.js.map +7 -0
  79. package/utils/storage/getGlobalStorage.d.ts +1 -0
  80. package/utils/storage/getGlobalStorage.js +7 -0
  81. package/utils/storage/getGlobalStorage.js.map +7 -0
  82. package/utils/storage/getStorageKeys.d.ts +1 -0
  83. package/utils/storage/getStorageKeys.js +12 -0
  84. package/utils/storage/getStorageKeys.js.map +7 -0
  85. package/utils/storage/index.d.ts +8 -0
  86. package/utils/storage/index.js +62 -0
  87. package/utils/storage/index.js.map +7 -0
  88. package/utils/storage/providers.d.ts +11 -0
  89. package/utils/storage/providers.js +30 -0
  90. package/utils/storage/providers.js.map +7 -0
  91. package/utils/storage/serializers.d.ts +2 -0
  92. package/utils/storage/serializers.js +14 -0
  93. package/utils/storage/serializers.js.map +7 -0
  94. package/utils/storage/types.d.ts +33 -0
  95. package/utils/storage/types.js +1 -0
  96. package/utils/storage/types.js.map +7 -0
  97. package/utils/storage.d.ts +1 -0
  98. package/utils/storage.js +2 -0
  99. package/utils/storage.js.map +7 -0
  100. package/utils/string/changeCase.js +1 -1
  101. package/utils/string/changeCase.js.map +2 -2
  102. package/utils/tween.js +5 -5
  103. package/utils/tween.js.map +2 -2
  104. package/tsconfig.build.tsbuildinfo +0 -1
@@ -1,7 +1,8 @@
1
1
  import { isArray } from "../utils/index.js";
2
2
  function getDirectChildren(parentInstance, parentName, childrenName) {
3
- const children = parentInstance.$children[childrenName];
4
- const nestedParents = parentInstance.$children[parentName];
3
+ const allChildren = parentInstance.__children.props;
4
+ const children = allChildren[childrenName];
5
+ const nestedParents = allChildren[parentName];
5
6
  if (!isArray(children)) {
6
7
  return [];
7
8
  }
@@ -11,7 +12,7 @@ function getDirectChildren(parentInstance, parentName, childrenName) {
11
12
  const directChildren = [];
12
13
  for (const child of children) {
13
14
  for (const nestedParent of nestedParents) {
14
- const nestedChildren = nestedParent.$children[childrenName];
15
+ const nestedChildren = nestedParent.__children.props[childrenName];
15
16
  if (isArray(nestedChildren) && nestedChildren.includes(child)) {
16
17
  continue;
17
18
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../packages/js-toolkit/helpers/getDirectChildren.ts"],
4
- "sourcesContent": ["import { Base, BaseProps } from '../Base/index.js';\nimport { isArray } from '../utils/index.js';\n\n/**\n * Get direct children from a parent when working with nested components.\n *\n * @template {Base} T\n * @param {Base} parentInstance\n * @param {string} parentName\n * @param {string} childrenName\n * @return {T[]}\n * @link https://js-toolkit.studiometa.dev/api/helpers/getDirectChildren.html\n*/\nexport function getDirectChildren<T extends Base = Base>(\n parentInstance: Base,\n parentName: string,\n childrenName: string,\n): T[] {\n const children = parentInstance.$children[childrenName] as Base<BaseProps>[];\n const nestedParents = parentInstance.$children[parentName] as Base<BaseProps>[];\n\n if (!isArray(children)) {\n return [];\n }\n\n if (!isArray(nestedParents) || nestedParents.length <= 0) {\n return children as T[];\n }\n\n const directChildren = [];\n\n for (const child of children) {\n for (const nestedParent of nestedParents) {\n const nestedChildren = nestedParent.$children[childrenName] as Base<BaseProps>[];\n\n if (isArray(nestedChildren) && nestedChildren.includes(child)) {\n continue;\n }\n\n directChildren.push(child);\n }\n }\n\n return directChildren;\n}\n\n/**\n * Test if a component instance is a direct child from the given component.\n *\n * @param {Base} parentInstance\n * @param {string} parentName\n * @param {string} childrenName\n * @param {Base} childInstance\n * @return {boolean}\n * @link https://js-toolkit.studiometa.dev/api/helpers/isDirectChild.html\n*/\nexport function isDirectChild(parentInstance, parentName, childrenName, childInstance) {\n return getDirectChildren(parentInstance, parentName, childrenName).includes(childInstance);\n}\n"],
5
- "mappings": "AACA,SAAS,eAAe;AAYjB,SAAS,kBACd,gBACA,YACA,cACK;AACL,QAAM,WAAW,eAAe,UAAU,YAAY;AACtD,QAAM,gBAAgB,eAAe,UAAU,UAAU;AAEzD,MAAI,CAAC,QAAQ,QAAQ,GAAG;AACtB,WAAO,CAAC;AAAA,EACV;AAEA,MAAI,CAAC,QAAQ,aAAa,KAAK,cAAc,UAAU,GAAG;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,iBAAiB,CAAC;AAExB,aAAW,SAAS,UAAU;AAC5B,eAAW,gBAAgB,eAAe;AACxC,YAAM,iBAAiB,aAAa,UAAU,YAAY;AAE1D,UAAI,QAAQ,cAAc,KAAK,eAAe,SAAS,KAAK,GAAG;AAC7D;AAAA,MACF;AAEA,qBAAe,KAAK,KAAK;AAAA,IAC3B;AAAA,EACF;AAEA,SAAO;AACT;AAYO,SAAS,cAAc,gBAAgB,YAAY,cAAc,eAAe;AACrF,SAAO,kBAAkB,gBAAgB,YAAY,YAAY,EAAE,SAAS,aAAa;AAC3F;",
4
+ "sourcesContent": ["import { Base, BaseProps } from '../Base/index.js';\nimport { isArray } from '../utils/index.js';\n\n/**\n * Get direct children from a parent when working with nested components.\n *\n * @template {Base} T\n * @param {Base} parentInstance\n * @param {string} parentName\n * @param {string} childrenName\n * @return {T[]}\n * @link https://js-toolkit.studiometa.dev/api/helpers/getDirectChildren.html\n*/\nexport function getDirectChildren<T extends Base = Base>(\n parentInstance: Base,\n parentName: string,\n childrenName: string,\n): T[] {\n const allChildren = parentInstance.__children.props;\n const children = allChildren[childrenName] as Base<BaseProps>[];\n const nestedParents = allChildren[parentName] as Base<BaseProps>[];\n\n if (!isArray(children)) {\n return [];\n }\n\n if (!isArray(nestedParents) || nestedParents.length <= 0) {\n return children as T[];\n }\n\n const directChildren = [];\n\n for (const child of children) {\n for (const nestedParent of nestedParents) {\n const nestedChildren = nestedParent.__children.props[childrenName] as Base<BaseProps>[];\n\n if (isArray(nestedChildren) && nestedChildren.includes(child)) {\n continue;\n }\n\n directChildren.push(child);\n }\n }\n\n return directChildren;\n}\n\n/**\n * Test if a component instance is a direct child from the given component.\n *\n * @param {Base} parentInstance\n * @param {string} parentName\n * @param {string} childrenName\n * @param {Base} childInstance\n * @return {boolean}\n * @link https://js-toolkit.studiometa.dev/api/helpers/isDirectChild.html\n*/\nexport function isDirectChild(parentInstance, parentName, childrenName, childInstance) {\n return getDirectChildren(parentInstance, parentName, childrenName).includes(childInstance);\n}\n"],
5
+ "mappings": "AACA,SAAS,eAAe;AAYjB,SAAS,kBACd,gBACA,YACA,cACK;AACL,QAAM,cAAc,eAAe,WAAW;AAC9C,QAAM,WAAW,YAAY,YAAY;AACzC,QAAM,gBAAgB,YAAY,UAAU;AAE5C,MAAI,CAAC,QAAQ,QAAQ,GAAG;AACtB,WAAO,CAAC;AAAA,EACV;AAEA,MAAI,CAAC,QAAQ,aAAa,KAAK,cAAc,UAAU,GAAG;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,iBAAiB,CAAC;AAExB,aAAW,SAAS,UAAU;AAC5B,eAAW,gBAAgB,eAAe;AACxC,YAAM,iBAAiB,aAAa,WAAW,MAAM,YAAY;AAEjE,UAAI,QAAQ,cAAc,KAAK,eAAe,SAAS,KAAK,GAAG;AAC7D;AAAA,MACF;AAEA,qBAAe,KAAK,KAAK;AAAA,IAC3B;AAAA,EACF;AAEA,SAAO;AACT;AAYO,SAAS,cAAc,gBAAgB,YAAY,cAAc,eAAe;AACrF,SAAO,kBAAkB,gBAAgB,YAAY,YAAY,EAAE,SAAS,aAAa;AAC3F;",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,5 @@
1
1
  export * from './createApp.js';
2
+ export * from './defineFeatures.js';
2
3
  export * from './getClosestParent.js';
3
4
  export * from './getDirectChildren.js';
4
5
  export * from './getInstanceFromElement.js';
@@ -7,4 +8,6 @@ export * from './importOnMediaQuery.js';
7
8
  export * from './importWhenIdle.js';
8
9
  export * from './importWhenPrefersMotion.js';
9
10
  export * from './importWhenVisible.js';
11
+ export * from './logTree.js';
10
12
  export * from './registerComponent.js';
13
+ export { type QueryOptions, queryComponent, queryComponentAll, closestComponent, } from './queryComponent.js';
package/helpers/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./createApp.js";
2
+ export * from "./defineFeatures.js";
2
3
  export * from "./getClosestParent.js";
3
4
  export * from "./getDirectChildren.js";
4
5
  export * from "./getInstanceFromElement.js";
@@ -7,5 +8,16 @@ export * from "./importOnMediaQuery.js";
7
8
  export * from "./importWhenIdle.js";
8
9
  export * from "./importWhenPrefersMotion.js";
9
10
  export * from "./importWhenVisible.js";
11
+ export * from "./logTree.js";
10
12
  export * from "./registerComponent.js";
13
+ import {
14
+ queryComponent,
15
+ queryComponentAll,
16
+ closestComponent
17
+ } from "./queryComponent.js";
18
+ export {
19
+ closestComponent,
20
+ queryComponent,
21
+ queryComponentAll
22
+ };
11
23
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../packages/js-toolkit/helpers/index.ts"],
4
- "sourcesContent": ["export * from './createApp.js';\nexport * from './getClosestParent.js';\nexport * from './getDirectChildren.js';\nexport * from './getInstanceFromElement.js';\nexport * from './importOnInteraction.js';\nexport * from './importOnMediaQuery.js';\nexport * from './importWhenIdle.js';\nexport * from './importWhenPrefersMotion.js';\nexport * from './importWhenVisible.js';\nexport * from './registerComponent.js';\n"],
5
- "mappings": "AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
4
+ "sourcesContent": ["export * from './createApp.js';\nexport * from './defineFeatures.js';\nexport * from './getClosestParent.js';\nexport * from './getDirectChildren.js';\nexport * from './getInstanceFromElement.js';\nexport * from './importOnInteraction.js';\nexport * from './importOnMediaQuery.js';\nexport * from './importWhenIdle.js';\nexport * from './importWhenPrefersMotion.js';\nexport * from './importWhenVisible.js';\nexport * from './logTree.js';\nexport * from './registerComponent.js';\nexport {\n type QueryOptions,\n queryComponent,\n queryComponentAll,\n closestComponent,\n} from './queryComponent.js';\n"],
5
+ "mappings": "AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,OACK;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,17 @@
1
+ import type { Base } from '../Base/Base.js';
2
+ /**
3
+ * Log the component tree starting from the given instance.
4
+ *
5
+ * Uses the global instance registry and DOM containment to build the tree,
6
+ * without relying on the deprecated `$children` API.
7
+ *
8
+ * @param {Base} instance The root component instance.
9
+ * @example
10
+ * ```js
11
+ * import { createApp, logTree } from '@studiometa/js-toolkit';
12
+ *
13
+ * const useApp = createApp(App);
14
+ * useApp().then((app) => logTree(app));
15
+ * ```
16
+ */
17
+ export declare function logTree(instance: Base): void;
@@ -0,0 +1,59 @@
1
+ import { getInstances } from "../Base/index.js";
2
+ function buildTree(root) {
3
+ const allInstances = getInstances();
4
+ const rootNode = { instance: root, children: [] };
5
+ const descendants = [];
6
+ for (const instance of allInstances) {
7
+ if (instance !== root && root.$el.contains(instance.$el)) {
8
+ descendants.push(instance);
9
+ }
10
+ }
11
+ descendants.sort((a, b) => {
12
+ const position = a.$el.compareDocumentPosition(b.$el);
13
+ if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
14
+ if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;
15
+ return 0;
16
+ });
17
+ const nodeMap = /* @__PURE__ */ new Map();
18
+ nodeMap.set(root, rootNode);
19
+ for (const instance of descendants) {
20
+ const node = { instance, children: [] };
21
+ nodeMap.set(instance, node);
22
+ let parentNode = rootNode;
23
+ for (const [candidate, candidateNode] of nodeMap) {
24
+ if (candidate !== instance && candidate.$el.contains(instance.$el) && // Pick the most specific (deepest) container
25
+ parentNode.instance.$el.contains(candidate.$el)) {
26
+ parentNode = candidateNode;
27
+ }
28
+ }
29
+ parentNode.children.push(node);
30
+ }
31
+ return rootNode;
32
+ }
33
+ function logNode(node) {
34
+ const { instance } = node;
35
+ const mounted = instance.$isMounted;
36
+ const status = mounted ? "\u25CF" : "\u25CB";
37
+ const label = `${status} ${instance.$id}`;
38
+ if (node.children.length > 0) {
39
+ console.groupCollapsed(label, instance.$el);
40
+ for (const child of node.children) {
41
+ logNode(child);
42
+ }
43
+ console.groupEnd();
44
+ } else {
45
+ console.log(label, instance.$el);
46
+ }
47
+ }
48
+ function logTree(instance) {
49
+ const tree = buildTree(instance);
50
+ console.group(`\u{1F333} ${instance.$config.name}`);
51
+ for (const child of tree.children) {
52
+ logNode(child);
53
+ }
54
+ console.groupEnd();
55
+ }
56
+ export {
57
+ logTree
58
+ };
59
+ //# sourceMappingURL=logTree.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../packages/js-toolkit/helpers/logTree.ts"],
4
+ "sourcesContent": ["import type { Base } from '../Base/Base.js';\nimport { getInstances } from '../Base/index.js';\n\n/**\n * Build a tree structure from a flat set of instances using DOM containment.\n */\nfunction buildTree(root: Base): TreeNode {\n const allInstances = getInstances();\n const rootNode: TreeNode = { instance: root, children: [] };\n\n // Collect all instances that are descendants of root (excluding root itself)\n const descendants: Base[] = [];\n for (const instance of allInstances) {\n if (instance !== root && root.$el.contains(instance.$el)) {\n descendants.push(instance);\n }\n }\n\n // Sort by DOM order (depth-first)\n descendants.sort((a, b) => {\n const position = a.$el.compareDocumentPosition(b.$el);\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;\n if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;\n return 0;\n });\n\n // Build tree: assign each instance to its closest ancestor in the tree\n const nodeMap = new Map<Base, TreeNode>();\n nodeMap.set(root, rootNode);\n\n for (const instance of descendants) {\n const node: TreeNode = { instance, children: [] };\n nodeMap.set(instance, node);\n\n // Find the closest ancestor that is already in the tree\n let parentNode = rootNode;\n for (const [candidate, candidateNode] of nodeMap) {\n if (\n candidate !== instance &&\n candidate.$el.contains(instance.$el) &&\n // Pick the most specific (deepest) container\n parentNode.instance.$el.contains(candidate.$el)\n ) {\n parentNode = candidateNode;\n }\n }\n\n parentNode.children.push(node);\n }\n\n return rootNode;\n}\n\ninterface TreeNode {\n instance: Base;\n children: TreeNode[];\n}\n\n/**\n * Log a tree node and its children using console groups.\n */\nfunction logNode(node: TreeNode): void {\n const { instance } = node;\n const mounted = instance.$isMounted;\n const status = mounted ? '\u25CF' : '\u25CB';\n const label = `${status} ${instance.$id}`;\n\n if (node.children.length > 0) {\n console.groupCollapsed(label, instance.$el);\n for (const child of node.children) {\n logNode(child);\n }\n console.groupEnd();\n } else {\n console.log(label, instance.$el);\n }\n}\n\n/**\n * Log the component tree starting from the given instance.\n *\n * Uses the global instance registry and DOM containment to build the tree,\n * without relying on the deprecated `$children` API.\n *\n * @param {Base} instance The root component instance.\n * @example\n * ```js\n * import { createApp, logTree } from '@studiometa/js-toolkit';\n *\n * const useApp = createApp(App);\n * useApp().then((app) => logTree(app));\n * ```\n */\nexport function logTree(instance: Base): void {\n const tree = buildTree(instance);\n console.group(`\uD83C\uDF33 ${instance.$config.name}`);\n for (const child of tree.children) {\n logNode(child);\n }\n console.groupEnd();\n}\n"],
5
+ "mappings": "AACA,SAAS,oBAAoB;AAK7B,SAAS,UAAU,MAAsB;AACvC,QAAM,eAAe,aAAa;AAClC,QAAM,WAAqB,EAAE,UAAU,MAAM,UAAU,CAAC,EAAE;AAG1D,QAAM,cAAsB,CAAC;AAC7B,aAAW,YAAY,cAAc;AACnC,QAAI,aAAa,QAAQ,KAAK,IAAI,SAAS,SAAS,GAAG,GAAG;AACxD,kBAAY,KAAK,QAAQ;AAAA,IAC3B;AAAA,EACF;AAGA,cAAY,KAAK,CAAC,GAAG,MAAM;AACzB,UAAM,WAAW,EAAE,IAAI,wBAAwB,EAAE,GAAG;AACpD,QAAI,WAAW,KAAK,4BAA6B,QAAO;AACxD,QAAI,WAAW,KAAK,4BAA6B,QAAO;AACxD,WAAO;AAAA,EACT,CAAC;AAGD,QAAM,UAAU,oBAAI,IAAoB;AACxC,UAAQ,IAAI,MAAM,QAAQ;AAE1B,aAAW,YAAY,aAAa;AAClC,UAAM,OAAiB,EAAE,UAAU,UAAU,CAAC,EAAE;AAChD,YAAQ,IAAI,UAAU,IAAI;AAG1B,QAAI,aAAa;AACjB,eAAW,CAAC,WAAW,aAAa,KAAK,SAAS;AAChD,UACE,cAAc,YACd,UAAU,IAAI,SAAS,SAAS,GAAG;AAAA,MAEnC,WAAW,SAAS,IAAI,SAAS,UAAU,GAAG,GAC9C;AACA,qBAAa;AAAA,MACf;AAAA,IACF;AAEA,eAAW,SAAS,KAAK,IAAI;AAAA,EAC/B;AAEA,SAAO;AACT;AAUA,SAAS,QAAQ,MAAsB;AACrC,QAAM,EAAE,SAAS,IAAI;AACrB,QAAM,UAAU,SAAS;AACzB,QAAM,SAAS,UAAU,WAAM;AAC/B,QAAM,QAAQ,GAAG,MAAM,IAAI,SAAS,GAAG;AAEvC,MAAI,KAAK,SAAS,SAAS,GAAG;AAC5B,YAAQ,eAAe,OAAO,SAAS,GAAG;AAC1C,eAAW,SAAS,KAAK,UAAU;AACjC,cAAQ,KAAK;AAAA,IACf;AACA,YAAQ,SAAS;AAAA,EACnB,OAAO;AACL,YAAQ,IAAI,OAAO,SAAS,GAAG;AAAA,EACjC;AACF;AAiBO,SAAS,QAAQ,UAAsB;AAC5C,QAAM,OAAO,UAAU,QAAQ;AAC/B,UAAQ,MAAM,aAAM,SAAS,QAAQ,IAAI,EAAE;AAC3C,aAAW,SAAS,KAAK,UAAU;AACjC,YAAQ,KAAK;AAAA,EACf;AACA,UAAQ,SAAS;AACnB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,28 @@
1
+ import type { Base } from '../Base/index.js';
2
+ export interface QueryOptions {
3
+ from?: HTMLElement | Document;
4
+ }
5
+ export type ParsedQuery = {
6
+ name: string;
7
+ cssSelector?: string;
8
+ state?: string;
9
+ };
10
+ /**
11
+ * Parse a query string like 'Foo(.css-selector):mounted' into its parts.
12
+ */
13
+ export declare const parseQuery: (query: string) => ParsedQuery;
14
+ export declare function instanceIsMatching(instance: Base, parsedQuery: ParsedQuery): boolean;
15
+ /**
16
+ * Get the first instance of component with the given query.
17
+ */
18
+ export declare function queryComponent<T extends Base = Base>(query: string, options?: QueryOptions): T | undefined;
19
+ /**
20
+ * Get all instances of component with the given query.
21
+ */
22
+ export declare function queryComponentAll<T extends Base = Base>(query: string, options?: QueryOptions): T[];
23
+ /**
24
+ * Get the closest component instance by traversing up the DOM tree.
25
+ */
26
+ export declare function closestComponent<T extends Base = Base>(query: string, options?: {
27
+ from: HTMLElement;
28
+ }): T | undefined;
@@ -0,0 +1,66 @@
1
+ import { getInstances } from "../Base/index.js";
2
+ import { memo } from "../utils/memo.js";
3
+ import { getAncestorWhere } from "../utils/dom/ancestors.js";
4
+ const REGEX_QUERY = /([a-zA-Z0-9]+)(\((.*)\))?(:([a-z]+))?/;
5
+ const parseQuery = memo((query) => {
6
+ const [, name, , cssSelector, , state] = query.match(REGEX_QUERY);
7
+ return {
8
+ name,
9
+ cssSelector,
10
+ state
11
+ };
12
+ });
13
+ function instanceIsMatching(instance, parsedQuery) {
14
+ if (instance.$config.name !== parsedQuery.name) return false;
15
+ if (parsedQuery.cssSelector && !instance.$el.matches(parsedQuery.cssSelector)) return false;
16
+ if (parsedQuery.state === "mounted" && !instance.$isMounted) return false;
17
+ if (parsedQuery.state === "destroyed" && instance.$isMounted) return false;
18
+ return true;
19
+ }
20
+ function queryComponent(query, options = {}) {
21
+ const parsedQuery = parseQuery(query);
22
+ const { from = document } = options;
23
+ const instances = getInstances();
24
+ for (const instance of instances) {
25
+ if (!instanceIsMatching(instance, parsedQuery)) continue;
26
+ if (from !== document && !(from === instance.$el || from.contains(instance.$el))) continue;
27
+ return instance;
28
+ }
29
+ }
30
+ function queryComponentAll(query, options = {}) {
31
+ const parsedQuery = parseQuery(query);
32
+ const { from = document } = options;
33
+ const instances = getInstances();
34
+ const selectedInstances = /* @__PURE__ */ new Set();
35
+ for (const instance of instances) {
36
+ if (!instanceIsMatching(instance, parsedQuery)) continue;
37
+ if (from !== document && !(from === instance.$el || from.contains(instance.$el))) continue;
38
+ selectedInstances.add(instance);
39
+ }
40
+ return Array.from(selectedInstances);
41
+ }
42
+ function closestComponent(query, options = { from: null }) {
43
+ const { from } = options;
44
+ const parsedQuery = parseQuery(query);
45
+ let closestInstance;
46
+ getAncestorWhere(from, (element) => {
47
+ if (!element) return false;
48
+ const baseMap = element.__base__;
49
+ if (!baseMap) return false;
50
+ const instance = baseMap.get(parsedQuery.name);
51
+ if (instance && instance !== "terminated" && instanceIsMatching(instance, parsedQuery)) {
52
+ closestInstance = instance;
53
+ return true;
54
+ }
55
+ return false;
56
+ });
57
+ return closestInstance;
58
+ }
59
+ export {
60
+ closestComponent,
61
+ instanceIsMatching,
62
+ parseQuery,
63
+ queryComponent,
64
+ queryComponentAll
65
+ };
66
+ //# sourceMappingURL=queryComponent.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../packages/js-toolkit/helpers/queryComponent.ts"],
4
+ "sourcesContent": ["import type { Base, BaseEl } from '../Base/index.js';\nimport { getInstances } from '../Base/index.js';\nimport { memo } from '../utils/memo.js';\nimport { getAncestorWhere } from '../utils/dom/ancestors.js';\n\nexport interface QueryOptions {\n from?: HTMLElement | Document;\n}\n\ntype ComponentState = 'mounted' | 'destroyed' | 'terminated';\nexport type ParsedQuery = {\n name: string;\n cssSelector?: string;\n state?: string;\n};\n\nconst REGEX_QUERY = /([a-zA-Z0-9]+)(\\((.*)\\))?(:([a-z]+))?/;\n\n/**\n * Parse a query string like 'Foo(.css-selector):mounted' into its parts.\n */\nexport const parseQuery = memo((query: string): ParsedQuery => {\n const [, name, , cssSelector, , state] = query.match(REGEX_QUERY) as [\n string,\n string,\n string | undefined,\n string | undefined,\n string | undefined,\n ComponentState | undefined,\n ];\n\n return {\n name,\n cssSelector,\n state,\n };\n});\n\nexport function instanceIsMatching(instance: Base, parsedQuery: ParsedQuery): boolean {\n if (instance.$config.name !== parsedQuery.name) return false;\n if (parsedQuery.cssSelector && !instance.$el.matches(parsedQuery.cssSelector)) return false;\n\n if (parsedQuery.state === 'mounted' && !instance.$isMounted) return false;\n if (parsedQuery.state === 'destroyed' && instance.$isMounted) return false;\n\n return true;\n}\n\n/**\n * Get the first instance of component with the given query.\n */\nexport function queryComponent<T extends Base = Base>(\n query: string,\n options: QueryOptions = {},\n): T | undefined {\n const parsedQuery = parseQuery(query);\n const { from = document } = options;\n const instances = getInstances() as ReadonlySet<T>;\n\n for (const instance of instances) {\n if (!instanceIsMatching(instance, parsedQuery)) continue;\n if (from !== document && !(from === instance.$el || from.contains(instance.$el))) continue;\n\n return instance;\n }\n}\n\n/**\n * Get all instances of component with the given query.\n */\nexport function queryComponentAll<T extends Base = Base>(\n query: string,\n options: QueryOptions = {},\n): T[] {\n const parsedQuery = parseQuery(query);\n const { from = document } = options;\n const instances = getInstances() as ReadonlySet<T>;\n const selectedInstances = new Set<T>();\n\n for (const instance of instances) {\n if (!instanceIsMatching(instance, parsedQuery)) continue;\n if (from !== document && !(from === instance.$el || from.contains(instance.$el))) continue;\n\n selectedInstances.add(instance);\n }\n\n return Array.from(selectedInstances);\n}\n\n/**\n * Get the closest component instance by traversing up the DOM tree.\n */\nexport function closestComponent<T extends Base = Base>(\n query: string,\n options: { from: HTMLElement } = { from: null },\n): T | undefined {\n const { from } = options;\n const parsedQuery = parseQuery(query);\n let closestInstance: T | undefined;\n\n getAncestorWhere(from, (element) => {\n if (!element) return false;\n\n // Use the element's __base__ map for O(1) lookup by component name\n const baseMap = (element as BaseEl).__base__;\n if (!baseMap) return false;\n\n const instance = baseMap.get(parsedQuery.name) as T;\n if (instance && instance !== ('terminated' as unknown) && instanceIsMatching(instance, parsedQuery)) {\n closestInstance = instance;\n return true;\n }\n\n return false;\n });\n\n return closestInstance;\n}\n"],
5
+ "mappings": "AACA,SAAS,oBAAoB;AAC7B,SAAS,YAAY;AACrB,SAAS,wBAAwB;AAajC,MAAM,cAAc;AAKb,MAAM,aAAa,KAAK,CAAC,UAA+B;AAC7D,QAAM,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,IAAI,MAAM,MAAM,WAAW;AAShE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF,CAAC;AAEM,SAAS,mBAAmB,UAAgB,aAAmC;AACpF,MAAI,SAAS,QAAQ,SAAS,YAAY,KAAM,QAAO;AACvD,MAAI,YAAY,eAAe,CAAC,SAAS,IAAI,QAAQ,YAAY,WAAW,EAAG,QAAO;AAEtF,MAAI,YAAY,UAAU,aAAa,CAAC,SAAS,WAAY,QAAO;AACpE,MAAI,YAAY,UAAU,eAAe,SAAS,WAAY,QAAO;AAErE,SAAO;AACT;AAKO,SAAS,eACd,OACA,UAAwB,CAAC,GACV;AACf,QAAM,cAAc,WAAW,KAAK;AACpC,QAAM,EAAE,OAAO,SAAS,IAAI;AAC5B,QAAM,YAAY,aAAa;AAE/B,aAAW,YAAY,WAAW;AAChC,QAAI,CAAC,mBAAmB,UAAU,WAAW,EAAG;AAChD,QAAI,SAAS,YAAY,EAAE,SAAS,SAAS,OAAO,KAAK,SAAS,SAAS,GAAG,GAAI;AAElF,WAAO;AAAA,EACT;AACF;AAKO,SAAS,kBACd,OACA,UAAwB,CAAC,GACpB;AACL,QAAM,cAAc,WAAW,KAAK;AACpC,QAAM,EAAE,OAAO,SAAS,IAAI;AAC5B,QAAM,YAAY,aAAa;AAC/B,QAAM,oBAAoB,oBAAI,IAAO;AAErC,aAAW,YAAY,WAAW;AAChC,QAAI,CAAC,mBAAmB,UAAU,WAAW,EAAG;AAChD,QAAI,SAAS,YAAY,EAAE,SAAS,SAAS,OAAO,KAAK,SAAS,SAAS,GAAG,GAAI;AAElF,sBAAkB,IAAI,QAAQ;AAAA,EAChC;AAEA,SAAO,MAAM,KAAK,iBAAiB;AACrC;AAKO,SAAS,iBACd,OACA,UAAiC,EAAE,MAAM,KAAK,GAC/B;AACf,QAAM,EAAE,KAAK,IAAI;AACjB,QAAM,cAAc,WAAW,KAAK;AACpC,MAAI;AAEJ,mBAAiB,MAAM,CAAC,YAAY;AAClC,QAAI,CAAC,QAAS,QAAO;AAGrB,UAAM,UAAW,QAAmB;AACpC,QAAI,CAAC,QAAS,QAAO;AAErB,UAAM,WAAW,QAAQ,IAAI,YAAY,IAAI;AAC7C,QAAI,YAAY,aAAc,gBAA4B,mBAAmB,UAAU,WAAW,GAAG;AACnG,wBAAkB;AAClB,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT,CAAC;AAED,SAAO;AACT;",
6
+ "names": []
7
+ }
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const version: "3.4.3";
1
+ export declare const version: '3.6.0-beta.0';
2
2
  export * from './Base/index.js';
3
3
  export * from './Base/types.js';
4
4
  export * from './decorators/index.js';
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "3.4.3";
1
+ const version = "3.6.0-beta.0";
2
2
  export * from "./Base/index.js";
3
3
  export * from "./Base/types.js";
4
4
  export * from "./decorators/index.js";
package/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../packages/js-toolkit/index.ts"],
4
- "sourcesContent": ["export const version = '3.4.3' as const;\n\nexport * from './Base/index.js';\nexport * from './Base/types.js';\nexport * from './decorators/index.js';\nexport * from './helpers/index.js';\nexport * from './services/index.js';\n"],
4
+ "sourcesContent": ["export const version = '3.6.0-beta.0' as const;\n\nexport * from './Base/index.js';\nexport * from './Base/types.js';\nexport * from './decorators/index.js';\nexport * from './helpers/index.js';\nexport * from './services/index.js';\n"],
5
5
  "mappings": "AAAO,MAAM,UAAU;AAEvB,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studiometa/js-toolkit",
3
- "version": "3.4.3",
3
+ "version": "3.6.0-beta.0",
4
4
  "description": "A set of useful little bits of JavaScript to boost your project! 🚀",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -82,7 +82,7 @@ export declare class AbstractService<PropsType = any> {
82
82
  /**
83
83
  * Implements the EventListenerObject interface.
84
84
  */
85
- handleEvent(event: Event): void;
85
+ handleEvent(_event: Event): void;
86
86
  /**
87
87
  * Add or remove event listeners based on the static `config` property.
88
88
  */
@@ -82,7 +82,7 @@ class AbstractService {
82
82
  /**
83
83
  * Implements the EventListenerObject interface.
84
84
  */
85
- handleEvent(event) {
85
+ handleEvent(_event) {
86
86
  }
87
87
  /**
88
88
  * Add or remove event listeners based on the static `config` property.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../packages/js-toolkit/services/AbstractService.ts"],
4
- "sourcesContent": ["import { cache } from '../utils/cache.js';\n\nexport interface ServiceInterface<T> {\n /**\n * Remove a function from the resize service by its key.\n */\n remove(key: string | symbol): void;\n /**\n * Add a callback to the service. The callback will receive the current service props as parameter.\n */\n add(key: string | symbol, callback: (props: T) => void): void;\n /**\n * Test if the service has alreaydy a callback for the given key.\n */\n has(key: string | symbol): boolean;\n /**\n * Get the service's props.\n */\n props(): T;\n}\n\n/**\n * Service configuration of events to be attached to targets.\n */\nexport type ServiceConfig = [\n (instance: AbstractService) => EventTarget,\n [string, AddEventListenerOptions?][],\n][];\n\n/**\n * AbstractService class.\n */\nexport class AbstractService<PropsType = any> {\n /**\n * Used to type `this.constructor` correctly\n * @link https://github.com/microsoft/TypeScript/issues/3841#issuecomment-2381594311\n */\n declare ['constructor']: typeof AbstractService;\n\n /**\n * Service configuration.\n */\n static config: ServiceConfig = [];\n\n /**\n * Cache for the created instances.\n */\n static __instances = new Map();\n\n /**\n * Get a service instance as a singleton based on the given key.\n */\n static getInstance<T extends ServiceInterface<any>>(keys: any = [this], ...args: any[]) {\n return cache(keys, () => {\n // @ts-ignore\n const instance = new this(...args);\n return {\n add: (key, callback) => instance.add(key, callback),\n remove: (key) => instance.remove(key),\n has: (key) => instance.has(key),\n props: () => instance.props,\n } as T;\n });\n }\n\n /**\n * Is the service active or not?\n */\n __isInit = false;\n\n /**\n * Props for the service.\n */\n props = {};\n\n /**\n * Holds all the callbacks that will be triggered.\n */\n callbacks: Map<string | symbol, (props: PropsType) => unknown> = new Map();\n\n /**\n * Does the service has the given key?\n */\n has(key: string | symbol): boolean {\n return this.callbacks.has(key);\n }\n\n /**\n * Get a service callback by its key.\n */\n get(key: string | symbol): (props: PropsType) => unknown {\n return this.callbacks.get(key);\n }\n\n /**\n * Add a callback to the service.\n */\n add(key: string | symbol, callback: (props: PropsType) => unknown) {\n if (this.has(key)) {\n console.warn(`The key \\`${String(key)}\\` has already been added.`);\n return;\n }\n\n // Initialize the service when we add the first callback\n if (this.callbacks.size === 0 && !this.__isInit) {\n this.init();\n this.__isInit = true;\n }\n\n this.callbacks.set(key, callback);\n }\n\n /**\n * Remove a callback from the service by its key.\n */\n remove(key: string | symbol) {\n this.callbacks.delete(key);\n\n // Kill the service when we remove the last callback\n if (this.callbacks.size === 0 && this.__isInit) {\n this.kill();\n this.__isInit = false;\n }\n }\n\n /**\n * Trigger all service callbacks with service props.\n */\n trigger(props: PropsType) {\n for (const callback of this.callbacks.values()) {\n callback(props);\n }\n }\n\n /* v8 ignore next 6 */\n /**\n * Implements the EventListenerObject interface.\n */\n handleEvent(event: Event) {\n // Should be implemented.\n }\n\n /**\n * Add or remove event listeners based on the static `config` property.\n */\n __manageEvents(mode: 'add' | 'remove') {\n for (const [target, events] of this.constructor.config) {\n const resolvedTarget = target(this);\n for (const [type, options] of events) {\n resolvedTarget[`${mode}EventListener`](type, this, options);\n }\n }\n }\n\n /**\n * Triggered when the service is initialized.\n */\n init() {\n this.__manageEvents('add');\n }\n\n /**\n * Triggered when the service is killed.\n */\n kill() {\n this.__manageEvents('remove');\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,aAAa;AAgCf,MAAM,gBAAiC;AAAA;AAAA;AAAA;AAAA,EAU5C,OAAO,SAAwB,CAAC;AAAA;AAAA;AAAA;AAAA,EAKhC,OAAO,cAAc,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA,EAK7B,OAAO,YAA6C,OAAY,CAAC,IAAI,MAAM,MAAa;AACtF,WAAO,MAAM,MAAM,MAAM;AAEvB,YAAM,WAAW,IAAI,KAAK,GAAG,IAAI;AACjC,aAAO;AAAA,QACL,KAAK,CAAC,KAAK,aAAa,SAAS,IAAI,KAAK,QAAQ;AAAA,QAClD,QAAQ,CAAC,QAAQ,SAAS,OAAO,GAAG;AAAA,QACpC,KAAK,CAAC,QAAQ,SAAS,IAAI,GAAG;AAAA,QAC9B,OAAO,MAAM,SAAS;AAAA,MACxB;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW;AAAA;AAAA;AAAA;AAAA,EAKX,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA,EAKT,YAAiE,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA,EAKzE,IAAI,KAA+B;AACjC,WAAO,KAAK,UAAU,IAAI,GAAG;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,KAAqD;AACvD,WAAO,KAAK,UAAU,IAAI,GAAG;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,KAAsB,UAAyC;AACjE,QAAI,KAAK,IAAI,GAAG,GAAG;AACjB,cAAQ,KAAK,aAAa,OAAO,GAAG,CAAC,4BAA4B;AACjE;AAAA,IACF;AAGA,QAAI,KAAK,UAAU,SAAS,KAAK,CAAC,KAAK,UAAU;AAC/C,WAAK,KAAK;AACV,WAAK,WAAW;AAAA,IAClB;AAEA,SAAK,UAAU,IAAI,KAAK,QAAQ;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,KAAsB;AAC3B,SAAK,UAAU,OAAO,GAAG;AAGzB,QAAI,KAAK,UAAU,SAAS,KAAK,KAAK,UAAU;AAC9C,WAAK,KAAK;AACV,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,OAAkB;AACxB,eAAW,YAAY,KAAK,UAAU,OAAO,GAAG;AAC9C,eAAS,KAAK;AAAA,IAChB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,OAAc;AAAA,EAE1B;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,MAAwB;AACrC,eAAW,CAAC,QAAQ,MAAM,KAAK,KAAK,YAAY,QAAQ;AACtD,YAAM,iBAAiB,OAAO,IAAI;AAClC,iBAAW,CAAC,MAAM,OAAO,KAAK,QAAQ;AACpC,uBAAe,GAAG,IAAI,eAAe,EAAE,MAAM,MAAM,OAAO;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO;AACL,SAAK,eAAe,KAAK;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO;AACL,SAAK,eAAe,QAAQ;AAAA,EAC9B;AACF;",
4
+ "sourcesContent": ["import { cache } from '../utils/cache.js';\n\nexport interface ServiceInterface<T> {\n /**\n * Remove a function from the resize service by its key.\n */\n remove(key: string | symbol): void;\n /**\n * Add a callback to the service. The callback will receive the current service props as parameter.\n */\n add(key: string | symbol, callback: (props: T) => void): void;\n /**\n * Test if the service has alreaydy a callback for the given key.\n */\n has(key: string | symbol): boolean;\n /**\n * Get the service's props.\n */\n props(): T;\n}\n\n/**\n * Service configuration of events to be attached to targets.\n */\nexport type ServiceConfig = [\n (instance: AbstractService) => EventTarget,\n [string, AddEventListenerOptions?][],\n][];\n\n/**\n * AbstractService class.\n */\nexport class AbstractService<PropsType = any> {\n /**\n * Used to type `this.constructor` correctly\n * @link https://github.com/microsoft/TypeScript/issues/3841#issuecomment-2381594311\n */\n declare ['constructor']: typeof AbstractService;\n\n /**\n * Service configuration.\n */\n static config: ServiceConfig = [];\n\n /**\n * Cache for the created instances.\n */\n static __instances = new Map();\n\n /**\n * Get a service instance as a singleton based on the given key.\n */\n static getInstance<T extends ServiceInterface<any>>(keys: any = [this], ...args: any[]) {\n return cache(keys, () => {\n // @ts-ignore\n const instance = new this(...args);\n return {\n add: (key, callback) => instance.add(key, callback),\n remove: (key) => instance.remove(key),\n has: (key) => instance.has(key),\n props: () => instance.props,\n } as T;\n });\n }\n\n /**\n * Is the service active or not?\n */\n __isInit = false;\n\n /**\n * Props for the service.\n */\n props = {};\n\n /**\n * Holds all the callbacks that will be triggered.\n */\n callbacks: Map<string | symbol, (props: PropsType) => unknown> = new Map();\n\n /**\n * Does the service has the given key?\n */\n has(key: string | symbol): boolean {\n return this.callbacks.has(key);\n }\n\n /**\n * Get a service callback by its key.\n */\n get(key: string | symbol): (props: PropsType) => unknown {\n return this.callbacks.get(key);\n }\n\n /**\n * Add a callback to the service.\n */\n add(key: string | symbol, callback: (props: PropsType) => unknown) {\n if (this.has(key)) {\n console.warn(`The key \\`${String(key)}\\` has already been added.`);\n return;\n }\n\n // Initialize the service when we add the first callback\n if (this.callbacks.size === 0 && !this.__isInit) {\n this.init();\n this.__isInit = true;\n }\n\n this.callbacks.set(key, callback);\n }\n\n /**\n * Remove a callback from the service by its key.\n */\n remove(key: string | symbol) {\n this.callbacks.delete(key);\n\n // Kill the service when we remove the last callback\n if (this.callbacks.size === 0 && this.__isInit) {\n this.kill();\n this.__isInit = false;\n }\n }\n\n /**\n * Trigger all service callbacks with service props.\n */\n trigger(props: PropsType) {\n for (const callback of this.callbacks.values()) {\n callback(props);\n }\n }\n\n /* v8 ignore next 6 */\n /**\n * Implements the EventListenerObject interface.\n */\n handleEvent(_event: Event) {\n // Should be implemented.\n }\n\n /**\n * Add or remove event listeners based on the static `config` property.\n */\n __manageEvents(mode: 'add' | 'remove') {\n for (const [target, events] of this.constructor.config) {\n const resolvedTarget = target(this);\n for (const [type, options] of events) {\n resolvedTarget[`${mode}EventListener`](type, this, options);\n }\n }\n }\n\n /**\n * Triggered when the service is initialized.\n */\n init() {\n this.__manageEvents('add');\n }\n\n /**\n * Triggered when the service is killed.\n */\n kill() {\n this.__manageEvents('remove');\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,aAAa;AAgCf,MAAM,gBAAiC;AAAA;AAAA;AAAA;AAAA,EAU5C,OAAO,SAAwB,CAAC;AAAA;AAAA;AAAA;AAAA,EAKhC,OAAO,cAAc,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA,EAK7B,OAAO,YAA6C,OAAY,CAAC,IAAI,MAAM,MAAa;AACtF,WAAO,MAAM,MAAM,MAAM;AAEvB,YAAM,WAAW,IAAI,KAAK,GAAG,IAAI;AACjC,aAAO;AAAA,QACL,KAAK,CAAC,KAAK,aAAa,SAAS,IAAI,KAAK,QAAQ;AAAA,QAClD,QAAQ,CAAC,QAAQ,SAAS,OAAO,GAAG;AAAA,QACpC,KAAK,CAAC,QAAQ,SAAS,IAAI,GAAG;AAAA,QAC9B,OAAO,MAAM,SAAS;AAAA,MACxB;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW;AAAA;AAAA;AAAA;AAAA,EAKX,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA,EAKT,YAAiE,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA,EAKzE,IAAI,KAA+B;AACjC,WAAO,KAAK,UAAU,IAAI,GAAG;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,KAAqD;AACvD,WAAO,KAAK,UAAU,IAAI,GAAG;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,KAAsB,UAAyC;AACjE,QAAI,KAAK,IAAI,GAAG,GAAG;AACjB,cAAQ,KAAK,aAAa,OAAO,GAAG,CAAC,4BAA4B;AACjE;AAAA,IACF;AAGA,QAAI,KAAK,UAAU,SAAS,KAAK,CAAC,KAAK,UAAU;AAC/C,WAAK,KAAK;AACV,WAAK,WAAW;AAAA,IAClB;AAEA,SAAK,UAAU,IAAI,KAAK,QAAQ;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,KAAsB;AAC3B,SAAK,UAAU,OAAO,GAAG;AAGzB,QAAI,KAAK,UAAU,SAAS,KAAK,KAAK,UAAU;AAC9C,WAAK,KAAK;AACV,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,OAAkB;AACxB,eAAW,YAAY,KAAK,UAAU,OAAO,GAAG;AAC9C,eAAS,KAAK;AAAA,IAChB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,QAAe;AAAA,EAE3B;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,MAAwB;AACrC,eAAW,CAAC,QAAQ,MAAM,KAAK,KAAK,YAAY,QAAQ;AACtD,YAAM,iBAAiB,OAAO,IAAI;AAClC,iBAAW,CAAC,MAAM,OAAO,KAAK,QAAQ;AACpC,uBAAe,GAAG,IAAI,eAAe,EAAE,MAAM,MAAM,OAAO;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO;AACL,SAAK,eAAe,KAAK;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO;AACL,SAAK,eAAe,QAAQ;AAAA,EAC9B;AACF;",
6
6
  "names": []
7
7
  }
@@ -71,11 +71,11 @@ export interface DragServiceOptions {
71
71
  }
72
72
  export declare class DragService extends AbstractService<DragServiceProps> {
73
73
  static MODES: {
74
- readonly START: "start";
75
- readonly DRAG: "drag";
76
- readonly DROP: "drop";
77
- readonly INERTIA: "inertia";
78
- readonly STOP: "stop";
74
+ readonly START: 'start';
75
+ readonly DRAG: 'drag';
76
+ readonly DROP: 'drop';
77
+ readonly INERTIA: 'inertia';
78
+ readonly STOP: 'stop';
79
79
  };
80
80
  static config: ServiceConfig;
81
81
  id: string;
@@ -13,7 +13,7 @@ export declare class RafService extends AbstractService<RafServiceProps> {
13
13
  /**
14
14
  * @private
15
15
  */
16
- scheduler: Record<"read" | "write" | "afterWrite", (fn: () => unknown) => void>;
16
+ scheduler: Record<"afterWrite" | "read" | "write", (fn: () => unknown) => void>;
17
17
  props: RafServiceProps;
18
18
  trigger(props: RafServiceProps): void;
19
19
  loop(): void;
@@ -15,16 +15,22 @@ class RafService extends AbstractService {
15
15
  delta: 0
16
16
  };
17
17
  trigger(props) {
18
- for (const callback of this.callbacks.values()) {
19
- this.scheduler.read(() => {
18
+ this.scheduler.read(() => {
19
+ const writeQueue = [];
20
+ for (const callback of this.callbacks.values()) {
20
21
  const render = callback(props);
21
22
  if (isFunction(render)) {
22
- this.scheduler.write(() => {
23
- render(props);
24
- });
23
+ writeQueue.push(render);
25
24
  }
26
- });
27
- }
25
+ }
26
+ if (writeQueue.length > 0) {
27
+ this.scheduler.write(() => {
28
+ for (let i = 0; i < writeQueue.length; i++) {
29
+ writeQueue[i](props);
30
+ }
31
+ });
32
+ }
33
+ });
28
34
  }
29
35
  loop() {
30
36
  const time = performance.now();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../packages/js-toolkit/services/RafService.ts"],
4
- "sourcesContent": ["import type { ServiceInterface } from './AbstractService.js';\nimport { AbstractService } from './AbstractService.js';\nimport { domScheduler } from '../utils/scheduler.js';\nimport { isFunction } from '../utils/is.js';\n\nexport interface RafServiceProps {\n time: DOMHighResTimeStamp;\n delta: number;\n}\n\nexport type RafServiceInterface = ServiceInterface<RafServiceProps>;\n\nexport class RafService extends AbstractService<RafServiceProps> {\n /**\n * @private\n */\n isTicking = false;\n\n /**\n * @private\n */\n scheduler = domScheduler;\n\n props: RafServiceProps = {\n time: performance.now(),\n delta: 0,\n };\n\n trigger(props: RafServiceProps) {\n for (const callback of this.callbacks.values()) {\n this.scheduler.read(() => {\n const render = callback(props) as (() => unknown) | void;\n if (isFunction(render)) {\n this.scheduler.write(() => {\n // @ts-ignore\n render(props);\n });\n }\n });\n }\n }\n\n loop() {\n const time = performance.now();\n this.props.delta = time - this.props.time;\n this.props.time = time;\n this.trigger(this.props);\n\n if (!this.isTicking) {\n return;\n }\n\n requestAnimationFrame(() => this.loop());\n }\n\n init() {\n this.isTicking = true;\n requestAnimationFrame(() => this.loop());\n }\n\n kill() {\n this.isTicking = false;\n }\n}\n\n/**\n * Use the RequestAnimationFrame (raf) service.\n * @link https://js-toolkit.studiometa.dev/api/services/useRaf.html\n*/\nexport function useRaf(): RafServiceInterface {\n return RafService.getInstance();\n}\n"],
5
- "mappings": "AACA,SAAS,uBAAuB;AAChC,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AASpB,MAAM,mBAAmB,gBAAiC;AAAA;AAAA;AAAA;AAAA,EAI/D,YAAY;AAAA;AAAA;AAAA;AAAA,EAKZ,YAAY;AAAA,EAEZ,QAAyB;AAAA,IACvB,MAAM,YAAY,IAAI;AAAA,IACtB,OAAO;AAAA,EACT;AAAA,EAEA,QAAQ,OAAwB;AAC9B,eAAW,YAAY,KAAK,UAAU,OAAO,GAAG;AAC9C,WAAK,UAAU,KAAK,MAAM;AACxB,cAAM,SAAS,SAAS,KAAK;AAC7B,YAAI,WAAW,MAAM,GAAG;AACtB,eAAK,UAAU,MAAM,MAAM;AAEzB,mBAAO,KAAK;AAAA,UACd,CAAC;AAAA,QACH;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,OAAO;AACL,UAAM,OAAO,YAAY,IAAI;AAC7B,SAAK,MAAM,QAAQ,OAAO,KAAK,MAAM;AACrC,SAAK,MAAM,OAAO;AAClB,SAAK,QAAQ,KAAK,KAAK;AAEvB,QAAI,CAAC,KAAK,WAAW;AACnB;AAAA,IACF;AAEA,0BAAsB,MAAM,KAAK,KAAK,CAAC;AAAA,EACzC;AAAA,EAEA,OAAO;AACL,SAAK,YAAY;AACjB,0BAAsB,MAAM,KAAK,KAAK,CAAC;AAAA,EACzC;AAAA,EAEA,OAAO;AACL,SAAK,YAAY;AAAA,EACnB;AACF;AAMO,SAAS,SAA8B;AAC5C,SAAO,WAAW,YAAY;AAChC;",
4
+ "sourcesContent": ["import type { ServiceInterface } from './AbstractService.js';\nimport { AbstractService } from './AbstractService.js';\nimport { domScheduler } from '../utils/scheduler.js';\nimport { isFunction } from '../utils/is.js';\n\nexport interface RafServiceProps {\n time: DOMHighResTimeStamp;\n delta: number;\n}\n\nexport type RafServiceInterface = ServiceInterface<RafServiceProps>;\n\nexport class RafService extends AbstractService<RafServiceProps> {\n /**\n * @private\n */\n isTicking = false;\n\n /**\n * @private\n */\n scheduler = domScheduler;\n\n props: RafServiceProps = {\n time: performance.now(),\n delta: 0,\n };\n\n trigger(props: RafServiceProps) {\n this.scheduler.read(() => {\n const writeQueue: Array<(props: RafServiceProps) => unknown> = [];\n\n for (const callback of this.callbacks.values()) {\n const render = callback(props) as ((props: RafServiceProps) => unknown) | void;\n if (isFunction(render)) {\n writeQueue.push(render);\n }\n }\n\n if (writeQueue.length > 0) {\n this.scheduler.write(() => {\n for (let i = 0; i < writeQueue.length; i++) {\n writeQueue[i](props);\n }\n });\n }\n });\n }\n\n loop() {\n const time = performance.now();\n this.props.delta = time - this.props.time;\n this.props.time = time;\n this.trigger(this.props);\n\n if (!this.isTicking) {\n return;\n }\n\n requestAnimationFrame(() => this.loop());\n }\n\n init() {\n this.isTicking = true;\n requestAnimationFrame(() => this.loop());\n }\n\n kill() {\n this.isTicking = false;\n }\n}\n\n/**\n * Use the RequestAnimationFrame (raf) service.\n * @link https://js-toolkit.studiometa.dev/api/services/useRaf.html\n*/\nexport function useRaf(): RafServiceInterface {\n return RafService.getInstance();\n}\n"],
5
+ "mappings": "AACA,SAAS,uBAAuB;AAChC,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AASpB,MAAM,mBAAmB,gBAAiC;AAAA;AAAA;AAAA;AAAA,EAI/D,YAAY;AAAA;AAAA;AAAA;AAAA,EAKZ,YAAY;AAAA,EAEZ,QAAyB;AAAA,IACvB,MAAM,YAAY,IAAI;AAAA,IACtB,OAAO;AAAA,EACT;AAAA,EAEA,QAAQ,OAAwB;AAC9B,SAAK,UAAU,KAAK,MAAM;AACxB,YAAM,aAAyD,CAAC;AAEhE,iBAAW,YAAY,KAAK,UAAU,OAAO,GAAG;AAC9C,cAAM,SAAS,SAAS,KAAK;AAC7B,YAAI,WAAW,MAAM,GAAG;AACtB,qBAAW,KAAK,MAAM;AAAA,QACxB;AAAA,MACF;AAEA,UAAI,WAAW,SAAS,GAAG;AACzB,aAAK,UAAU,MAAM,MAAM;AACzB,mBAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,uBAAW,CAAC,EAAE,KAAK;AAAA,UACrB;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,OAAO;AACL,UAAM,OAAO,YAAY,IAAI;AAC7B,SAAK,MAAM,QAAQ,OAAO,KAAK,MAAM;AACrC,SAAK,MAAM,OAAO;AAClB,SAAK,QAAQ,KAAK,KAAK;AAEvB,QAAI,CAAC,KAAK,WAAW;AACnB;AAAA,IACF;AAEA,0BAAsB,MAAM,KAAK,KAAK,CAAC;AAAA,EACzC;AAAA,EAEA,OAAO;AACL,SAAK,YAAY;AACjB,0BAAsB,MAAM,KAAK,KAAK,CAAC;AAAA,EACzC;AAAA,EAEA,OAAO;AACL,SAAK,YAAY;AAAA,EACnB;AACF;AAMO,SAAS,SAA8B;AAC5C,SAAO,WAAW,YAAY;AAChC;",
6
6
  "names": []
7
7
  }
@@ -45,14 +45,32 @@ export interface ScrollServiceProps {
45
45
  export type ScrollServiceInterface = ServiceInterface<ScrollServiceProps>;
46
46
  export declare class ScrollService extends AbstractService<ScrollServiceProps> {
47
47
  static config: ServiceConfig;
48
+ /**
49
+ * Cached scroll max values, updated on resize and first scroll.
50
+ * @private
51
+ */
52
+ __maxX: number | null;
53
+ __maxY: number | null;
54
+ /**
55
+ * Observe scroll container size changes that do not trigger window resize.
56
+ * @private
57
+ */
58
+ __resizeObserver: ResizeObserver | null;
48
59
  props: ScrollServiceProps;
49
60
  updateProps(): ScrollServiceProps;
50
- update(): void;
61
+ update(trigger?: boolean): void;
51
62
  onScrollDebounced: (...args: unknown[]) => void;
52
63
  /**
53
- * Scroll handler.
64
+ * Update cached max scroll values on resize.
65
+ * @private
66
+ */
67
+ __updateMaxValues(): void;
68
+ /**
69
+ * Scroll and resize handler.
54
70
  */
55
- handleEvent(): void;
71
+ handleEvent(event: Event): void;
72
+ init(): void;
73
+ kill(): void;
56
74
  }
57
75
  /**
58
76
  * Use the scroll service.
@@ -3,7 +3,21 @@ import { debounce } from "../utils/debounce.js";
3
3
  import { PASSIVE_CAPTURE_EVENT_OPTIONS } from "./utils.js";
4
4
  import { nextTick } from "../utils/nextTick.js";
5
5
  class ScrollService extends AbstractService {
6
- static config = [[() => document, [["scroll", PASSIVE_CAPTURE_EVENT_OPTIONS]]]];
6
+ static config = [
7
+ [() => document, [["scroll", PASSIVE_CAPTURE_EVENT_OPTIONS]]],
8
+ [() => window, [["resize", PASSIVE_CAPTURE_EVENT_OPTIONS]]]
9
+ ];
10
+ /**
11
+ * Cached scroll max values, updated on resize and first scroll.
12
+ * @private
13
+ */
14
+ __maxX = null;
15
+ __maxY = null;
16
+ /**
17
+ * Observe scroll container size changes that do not trigger window resize.
18
+ * @private
19
+ */
20
+ __resizeObserver = null;
7
21
  props = {
8
22
  x: window.scrollX,
9
23
  y: window.scrollY,
@@ -58,8 +72,11 @@ class ScrollService extends AbstractService {
58
72
  props.last.y = props.lastY = yLast;
59
73
  props.delta.x = props.deltaX = props.x - xLast;
60
74
  props.delta.y = props.deltaY = props.y - yLast;
61
- props.max.x = props.maxX = document.scrollingElement.scrollWidth - window.innerWidth;
62
- props.max.y = props.maxY = document.scrollingElement.scrollHeight - window.innerHeight;
75
+ if (this.__maxX === null || this.__maxY === null) {
76
+ this.__updateMaxValues();
77
+ }
78
+ props.max.x = props.maxX = this.__maxX;
79
+ props.max.y = props.maxY = this.__maxY;
63
80
  props.progress.x = props.progressX = props.max.x === 0 ? 1 : props.x / props.max.x;
64
81
  props.progress.y = props.progressY = props.max.y === 0 ? 1 : props.y / props.max.y;
65
82
  props.isUp = props.y < yLast;
@@ -70,19 +87,50 @@ class ScrollService extends AbstractService {
70
87
  props.direction.y = props.isDown ? "DOWN" : props.isUp ? "UP" : "NONE";
71
88
  return props;
72
89
  }
73
- update() {
74
- nextTick(() => this.updateProps()).then(() => this.trigger(this.props));
90
+ update(trigger = true) {
91
+ nextTick(() => this.updateProps()).then(() => {
92
+ if (trigger) {
93
+ this.trigger(this.props);
94
+ }
95
+ });
75
96
  }
76
97
  onScrollDebounced = debounce(() => {
77
98
  this.update();
78
99
  }, 100);
79
100
  /**
80
- * Scroll handler.
101
+ * Update cached max scroll values on resize.
102
+ * @private
81
103
  */
82
- handleEvent() {
104
+ __updateMaxValues() {
105
+ this.__maxX = document.scrollingElement.scrollWidth - window.innerWidth;
106
+ this.__maxY = document.scrollingElement.scrollHeight - window.innerHeight;
107
+ }
108
+ /**
109
+ * Scroll and resize handler.
110
+ */
111
+ handleEvent(event) {
112
+ if (event.type === "resize") {
113
+ this.__updateMaxValues();
114
+ this.update(false);
115
+ return;
116
+ }
83
117
  this.update();
84
118
  this.onScrollDebounced();
85
119
  }
120
+ init() {
121
+ super.init();
122
+ if (typeof ResizeObserver === "function" && document.scrollingElement) {
123
+ this.__resizeObserver = new ResizeObserver(() => {
124
+ this.__updateMaxValues();
125
+ });
126
+ this.__resizeObserver.observe(document.scrollingElement);
127
+ }
128
+ }
129
+ kill() {
130
+ this.__resizeObserver?.disconnect();
131
+ this.__resizeObserver = null;
132
+ super.kill();
133
+ }
86
134
  }
87
135
  function useScroll() {
88
136
  return ScrollService.getInstance();