@studiometa/ui 1.0.0-rc.3 → 1.0.0-rc.5

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/Menu/Menu.d.ts CHANGED
@@ -5,7 +5,7 @@ import { MenuList } from './MenuList.js';
5
5
  export interface MenuProps extends BaseProps {
6
6
  $children: {
7
7
  Menu: Menu[];
8
- MenutBtn: MenuBtn[];
8
+ MenuBtn: MenuBtn[];
9
9
  MenuList: MenuList[];
10
10
  };
11
11
  $options: {
@@ -45,6 +45,12 @@ export declare class Menu<T extends BaseProps = BaseProps> extends Base<T & Menu
45
45
  * Keyboard management.
46
46
  */
47
47
  keyed({ ENTER, ESC, isUp }: KeyServiceProps): void;
48
+ /**
49
+ * Close menu list on click outside.
50
+ */
51
+ onDocumentClick({ event }: {
52
+ event: MouseEvent;
53
+ }): void;
48
54
  /**
49
55
  * Toggle menu items on button click.
50
56
  */
package/Menu/Menu.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Base, isDirectChild, getDirectChildren } from "@studiometa/js-toolkit";
1
+ import { Base, getClosestParent } from "@studiometa/js-toolkit";
2
2
  import { nextTick } from "@studiometa/js-toolkit/utils";
3
3
  import { MenuBtn } from "./MenuBtn.js";
4
4
  import { MenuList } from "./MenuList.js";
@@ -10,8 +10,7 @@ class Menu extends Base {
10
10
  name: "Menu",
11
11
  components: {
12
12
  MenuBtn,
13
- MenuList,
14
- Menu
13
+ MenuList
15
14
  },
16
15
  options: {
17
16
  mode: {
@@ -25,13 +24,21 @@ class Menu extends Base {
25
24
  * Get the first `MenuList` instance.
26
25
  */
27
26
  get menuList() {
28
- return getDirectChildren(this, "Menu", "MenuList")[0];
27
+ for (const menuList of this.$children.MenuList) {
28
+ if (getClosestParent(menuList, this.constructor) === this) {
29
+ return menuList;
30
+ }
31
+ }
29
32
  }
30
33
  /**
31
34
  * Get the first `MenuBtn` instance.
32
35
  */
33
36
  get menuBtn() {
34
- return getDirectChildren(this, "Menu", "MenuBtn")[0];
37
+ for (const menuBtn of this.$children.MenuBtn) {
38
+ if (getClosestParent(menuBtn, this.constructor) === this) {
39
+ return menuBtn;
40
+ }
41
+ }
35
42
  }
36
43
  /**
37
44
  * Test which mode to use.
@@ -75,11 +82,20 @@ class Menu extends Base {
75
82
  }
76
83
  }
77
84
  }
85
+ /**
86
+ * Close menu list on click outside.
87
+ */
88
+ onDocumentClick({ event }) {
89
+ if (this.shouldReactOnClick && !this.$el.contains(event.target)) {
90
+ this.close();
91
+ }
92
+ }
78
93
  /**
79
94
  * Toggle menu items on button click.
80
95
  */
81
96
  onMenuBtnClick({ event, target }) {
82
- if (isDirectChild(this, "Menu", "MenuBtn", target) && this.shouldReactOnClick) {
97
+ if (!this.shouldReactOnClick) return;
98
+ if (getClosestParent(target, this.constructor) === this) {
83
99
  event.preventDefault();
84
100
  this.toggle();
85
101
  }
@@ -122,11 +138,11 @@ class Menu extends Base {
122
138
  * Close other non-parent menu items on menu items open.
123
139
  */
124
140
  onMenuListItemsOpen({ target }) {
125
- this.$children.MenuList.forEach((menuItem) => {
126
- if (!menuItem.$el.contains(target.$el)) {
127
- menuItem.close();
141
+ for (const menuList of this.$children.MenuList) {
142
+ if (!menuList.$el.contains(target.$el)) {
143
+ menuList.close();
128
144
  }
129
- });
145
+ }
130
146
  }
131
147
  /**
132
148
  * Close the menu.
package/Menu/Menu.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../packages/ui/Menu/Menu.ts"],
4
- "sourcesContent": ["import { Base, isDirectChild, getDirectChildren } from '@studiometa/js-toolkit';\nimport type { BaseConfig, BaseProps, KeyServiceProps } from '@studiometa/js-toolkit';\nimport { nextTick } from '@studiometa/js-toolkit/utils';\nimport { MenuBtn } from './MenuBtn.js';\nimport { MenuList } from './MenuList.js';\n\nexport interface MenuProps extends BaseProps {\n $children: {\n // eslint-disable-next-line no-use-before-define\n Menu: Menu[];\n MenutBtn: MenuBtn[];\n MenuList: MenuList[];\n };\n $options: {\n mode: 'click' | 'hover';\n };\n}\n\n/**\n * Menu class.\n */\nexport class Menu<T extends BaseProps = BaseProps> extends Base<T & MenuProps> {\n /**\n * Config.\n */\n static config: BaseConfig = {\n name: 'Menu',\n components: {\n MenuBtn,\n MenuList,\n Menu,\n },\n options: {\n mode: {\n type: String,\n default: 'click', // or 'hover'\n },\n },\n };\n\n /**\n * Get the first `MenuList` instance.\n */\n get menuList(): MenuList {\n return getDirectChildren<MenuList>(this, 'Menu', 'MenuList')[0];\n }\n\n /**\n * Get the first `MenuBtn` instance.\n */\n get menuBtn(): MenuBtn {\n return getDirectChildren<MenuBtn>(this, 'Menu', 'MenuBtn')[0];\n }\n\n /**\n * Test which mode to use.\n */\n get shouldReactOnClick(): boolean {\n return this.$options.mode === 'click';\n }\n\n /**\n * Wether the button or the items are hovered.\n */\n get isHover(): boolean {\n return this.menuBtn.isHover || this.menuList.isHover;\n }\n\n /**\n * Set attributes on mounted, destroy the component if it is missing required\n * child components.\n */\n mounted() {\n if (!this.menuBtn || !this.menuList) {\n return this.$destroy();\n }\n\n this.menuBtn.$el.setAttribute('aria-controls', this.$id);\n this.menuList.$el.setAttribute('id', this.$id);\n this.menuList.close();\n }\n\n /**\n * Keyboard management.\n */\n keyed({ ENTER, ESC, isUp }: KeyServiceProps) {\n if (!isUp) {\n return;\n }\n\n if (ESC) {\n this.close();\n return;\n }\n\n if (!this.shouldReactOnClick) {\n const hasFocusElementWithin = document.activeElement === this.menuBtn.$el;\n\n if (ENTER && hasFocusElementWithin) {\n this.toggle();\n }\n }\n }\n\n /**\n * Toggle menu items on button click.\n */\n onMenuBtnClick({ event, target }: { event: MouseEvent; target: MenuBtn }) {\n if (isDirectChild(this, 'Menu', 'MenuBtn', target) && this.shouldReactOnClick) {\n event.preventDefault();\n this.toggle();\n }\n }\n\n /**\n * Open menu items on button mouse enter.\n */\n onMenuBtnMouseenter({ target }: { target: MenuBtn }) {\n if (target === this.menuBtn && !this.shouldReactOnClick) {\n this.open();\n }\n }\n\n /**\n * Close menu items on button mouse leave.\n */\n onMenuBtnMouseleave() {\n if (this.shouldReactOnClick) {\n return;\n }\n\n nextTick(() => {\n if (!this.isHover) {\n this.close();\n }\n });\n }\n\n /**\n * Close menu items on button mouse leave.\n */\n onMenuListMouseleave() {\n if (this.shouldReactOnClick) {\n return;\n }\n\n nextTick(() => {\n if (!this.isHover) {\n this.close();\n }\n });\n }\n\n /**\n * Close other non-parent menu items on menu items open.\n */\n onMenuListItemsOpen({ target }: { target: MenuList }) {\n this.$children.MenuList.forEach((menuItem) => {\n if (!menuItem.$el.contains(target.$el)) {\n menuItem.close();\n }\n });\n }\n\n /**\n * Close the menu.\n */\n close() {\n this.menuList.close();\n }\n\n /**\n * Open the menu.\n */\n open() {\n this.menuList.open();\n }\n\n /**\n * Toggle the menu.\n */\n toggle() {\n this.menuList.toggle();\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Base, getClosestParent } from '@studiometa/js-toolkit';\nimport type { BaseConfig, BaseProps, KeyServiceProps } from '@studiometa/js-toolkit';\nimport { nextTick } from '@studiometa/js-toolkit/utils';\nimport { MenuBtn } from './MenuBtn.js';\nimport { MenuList } from './MenuList.js';\n\nexport interface MenuProps extends BaseProps {\n $children: {\n // eslint-disable-next-line no-use-before-define\n Menu: Menu[];\n MenuBtn: MenuBtn[];\n MenuList: MenuList[];\n };\n $options: {\n mode: 'click' | 'hover';\n };\n}\n\n/**\n * Menu class.\n */\nexport class Menu<T extends BaseProps = BaseProps> extends Base<T & MenuProps> {\n /**\n * Config.\n */\n static config: BaseConfig = {\n name: 'Menu',\n components: {\n MenuBtn,\n MenuList,\n },\n options: {\n mode: {\n type: String,\n default: 'click', // or 'hover'\n },\n },\n };\n\n /**\n * Get the first `MenuList` instance.\n */\n get menuList(): MenuList {\n for (const menuList of this.$children.MenuList) {\n if (getClosestParent(menuList, this.constructor) === this) {\n return menuList;\n }\n }\n }\n\n /**\n * Get the first `MenuBtn` instance.\n */\n get menuBtn(): MenuBtn {\n for (const menuBtn of this.$children.MenuBtn) {\n if (getClosestParent(menuBtn, this.constructor) === this) {\n return menuBtn;\n }\n }\n }\n\n /**\n * Test which mode to use.\n */\n get shouldReactOnClick(): boolean {\n return this.$options.mode === 'click';\n }\n\n /**\n * Wether the button or the items are hovered.\n */\n get isHover(): boolean {\n return this.menuBtn.isHover || this.menuList.isHover;\n }\n\n /**\n * Set attributes on mounted, destroy the component if it is missing required\n * child components.\n */\n mounted() {\n if (!this.menuBtn || !this.menuList) {\n return this.$destroy();\n }\n\n this.menuBtn.$el.setAttribute('aria-controls', this.$id);\n this.menuList.$el.setAttribute('id', this.$id);\n this.menuList.close();\n }\n\n /**\n * Keyboard management.\n */\n keyed({ ENTER, ESC, isUp }: KeyServiceProps) {\n if (!isUp) {\n return;\n }\n\n if (ESC) {\n this.close();\n return;\n }\n\n if (!this.shouldReactOnClick) {\n const hasFocusElementWithin = document.activeElement === this.menuBtn.$el;\n\n if (ENTER && hasFocusElementWithin) {\n this.toggle();\n }\n }\n }\n\n /**\n * Close menu list on click outside.\n */\n onDocumentClick({ event }: { event: MouseEvent }) {\n if (this.shouldReactOnClick && !this.$el.contains(event.target as Node)) {\n this.close();\n }\n }\n\n /**\n * Toggle menu items on button click.\n */\n onMenuBtnClick({ event, target }: { event: MouseEvent; target: MenuBtn }) {\n if (!this.shouldReactOnClick) return;\n\n if (getClosestParent(target, this.constructor) === this) {\n event.preventDefault();\n this.toggle();\n }\n }\n\n /**\n * Open menu items on button mouse enter.\n */\n onMenuBtnMouseenter({ target }: { target: MenuBtn }) {\n if (target === this.menuBtn && !this.shouldReactOnClick) {\n this.open();\n }\n }\n\n /**\n * Close menu items on button mouse leave.\n */\n onMenuBtnMouseleave() {\n if (this.shouldReactOnClick) {\n return;\n }\n\n nextTick(() => {\n if (!this.isHover) {\n this.close();\n }\n });\n }\n\n /**\n * Close menu items on button mouse leave.\n */\n onMenuListMouseleave() {\n if (this.shouldReactOnClick) {\n return;\n }\n\n nextTick(() => {\n if (!this.isHover) {\n this.close();\n }\n });\n }\n\n /**\n * Close other non-parent menu items on menu items open.\n */\n onMenuListItemsOpen({ target }: { target: MenuList }) {\n for (const menuList of this.$children.MenuList) {\n if (!menuList.$el.contains(target.$el)) {\n menuList.close();\n }\n }\n }\n\n /**\n * Close the menu.\n */\n close() {\n this.menuList.close();\n }\n\n /**\n * Open the menu.\n */\n open() {\n this.menuList.open();\n }\n\n /**\n * Toggle the menu.\n */\n toggle() {\n this.menuList.toggle();\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/Menu/MenuBtn.d.ts CHANGED
@@ -10,29 +10,16 @@ export declare class MenuBtn<T extends BaseProps = BaseProps> extends Base<T> {
10
10
  static config: BaseConfig;
11
11
  /**
12
12
  * Wether the button is hovered or not.
13
- * @type {boolean}
14
13
  */
15
14
  isHover: boolean;
16
- /**
17
- * Dispatch the click event.
18
- * @param {MouseEvent} event
19
- * @returns {void}
20
- */
21
- onClick({ event }: {
22
- event: MouseEvent;
23
- }): void;
24
15
  /**
25
16
  * Dispatch the mouseenter event.
26
- * @param {MouseEvent} event
27
- * @returns {void}
28
17
  */
29
18
  onMouseenter({ event }: {
30
19
  event: MouseEvent;
31
20
  }): void;
32
21
  /**
33
22
  * Dispatch the mouseleave event.
34
- * @param {MouseEvent} event
35
- * @returns {void}
36
23
  */
37
24
  onMouseleave({ event }: {
38
25
  event: MouseEvent;
package/Menu/MenuBtn.js CHANGED
@@ -8,21 +8,10 @@ class MenuBtn extends Base {
8
8
  };
9
9
  /**
10
10
  * Wether the button is hovered or not.
11
- * @type {boolean}
12
11
  */
13
12
  isHover = false;
14
- /**
15
- * Dispatch the click event.
16
- * @param {MouseEvent} event
17
- * @returns {void}
18
- */
19
- onClick({ event }) {
20
- event.stopPropagation();
21
- }
22
13
  /**
23
14
  * Dispatch the mouseenter event.
24
- * @param {MouseEvent} event
25
- * @returns {void}
26
15
  */
27
16
  onMouseenter({ event }) {
28
17
  this.isHover = true;
@@ -30,8 +19,6 @@ class MenuBtn extends Base {
30
19
  }
31
20
  /**
32
21
  * Dispatch the mouseleave event.
33
- * @param {MouseEvent} event
34
- * @returns {void}
35
22
  */
36
23
  onMouseleave({ event }) {
37
24
  this.isHover = false;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../packages/ui/Menu/MenuBtn.ts"],
4
- "sourcesContent": ["import { Base } from '@studiometa/js-toolkit';\nimport type { BaseConfig, BaseProps } from '@studiometa/js-toolkit';\n\n/**\n * MenuBtn class.\n */\nexport class MenuBtn<T extends BaseProps = BaseProps> extends Base<T> {\n /**\n * Config.\n */\n static config: BaseConfig = {\n name: 'MenuBtn',\n };\n\n /**\n * Wether the button is hovered or not.\n * @type {boolean}\n */\n isHover = false;\n\n /**\n * Dispatch the click event.\n * @param {MouseEvent} event\n * @returns {void}\n */\n onClick({ event }: { event: MouseEvent }) {\n event.stopPropagation();\n }\n\n /**\n * Dispatch the mouseenter event.\n * @param {MouseEvent} event\n * @returns {void}\n */\n onMouseenter({ event }: { event: MouseEvent }) {\n this.isHover = true;\n event.stopPropagation();\n }\n\n /**\n * Dispatch the mouseleave event.\n * @param {MouseEvent} event\n * @returns {void}\n */\n onMouseleave({ event }: { event: MouseEvent }) {\n this.isHover = false;\n event.stopPropagation();\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,YAAY;AAMd,MAAM,gBAAiD,KAAQ;AAAA;AAAA;AAAA;AAAA,EAIpE,OAAO,SAAqB;AAAA,IAC1B,MAAM;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOV,QAAQ,EAAE,MAAM,GAA0B;AACxC,UAAM,gBAAgB;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa,EAAE,MAAM,GAA0B;AAC7C,SAAK,UAAU;AACf,UAAM,gBAAgB;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa,EAAE,MAAM,GAA0B;AAC7C,SAAK,UAAU;AACf,UAAM,gBAAgB;AAAA,EACxB;AACF;",
4
+ "sourcesContent": ["import { Base } from '@studiometa/js-toolkit';\nimport type { BaseConfig, BaseProps } from '@studiometa/js-toolkit';\n\n/**\n * MenuBtn class.\n */\nexport class MenuBtn<T extends BaseProps = BaseProps> extends Base<T> {\n /**\n * Config.\n */\n static config: BaseConfig = {\n name: 'MenuBtn',\n };\n\n /**\n * Wether the button is hovered or not.\n */\n isHover = false;\n\n /**\n * Dispatch the mouseenter event.\n */\n onMouseenter({ event }: { event: MouseEvent }) {\n this.isHover = true;\n event.stopPropagation();\n }\n\n /**\n * Dispatch the mouseleave event.\n */\n onMouseleave({ event }: { event: MouseEvent }) {\n this.isHover = false;\n event.stopPropagation();\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,YAAY;AAMd,MAAM,gBAAiD,KAAQ;AAAA;AAAA;AAAA;AAAA,EAIpE,OAAO,SAAqB;AAAA,IAC1B,MAAM;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU;AAAA;AAAA;AAAA;AAAA,EAKV,aAAa,EAAE,MAAM,GAA0B;AAC7C,SAAK,UAAU;AACf,UAAM,gBAAgB;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,EAAE,MAAM,GAA0B;AAC7C,SAAK,UAAU;AACf,UAAM,gBAAgB;AAAA,EACxB;AACF;",
6
6
  "names": []
7
7
  }
@@ -50,7 +50,7 @@ export declare class MenuList<T extends BaseProps = BaseProps> extends Transitio
50
50
  */
51
51
  toggle(): void;
52
52
  /**
53
- * Update `tabindex` attribute of child focusable elements.
53
+ * Update `tabindex` attribute of children focusable elements.
54
54
  * @private
55
55
  */
56
56
  __updateTabIndexes(mode?: 'open' | 'close'): void;
@@ -58,5 +58,5 @@ export declare class MenuList<T extends BaseProps = BaseProps> extends Transitio
58
58
  * Filter out items which are inside a child `MenuList` instance.
59
59
  * @private
60
60
  */
61
- __filterFocusableItems(item: HTMLElement): boolean;
61
+ __isFocusableElementFromThisMenuList(item: HTMLElement): boolean;
62
62
  }
package/Menu/MenuList.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { getInstanceFromElement } from "@studiometa/js-toolkit";
1
2
  import { Transition } from "../Transition/index.js";
2
3
  const FOCUSABLE_ELEMENTS = [
3
4
  "a[href]:not([inert])",
@@ -67,13 +68,6 @@ class MenuList extends Transition {
67
68
  if (this.isOpen) {
68
69
  return;
69
70
  }
70
- const clickOutsideHandler = (event) => {
71
- if (!this.$el.contains(event.target)) {
72
- document.removeEventListener("click", clickOutsideHandler);
73
- this.close();
74
- }
75
- };
76
- document.addEventListener("click", clickOutsideHandler);
77
71
  this.__updateTabIndexes("open");
78
72
  this.$el.setAttribute("aria-hidden", "false");
79
73
  this.isOpen = true;
@@ -87,9 +81,9 @@ class MenuList extends Transition {
87
81
  if (!this.isOpen) {
88
82
  return;
89
83
  }
90
- this.$children.MenuList.forEach((menuItem) => {
91
- menuItem.close();
92
- });
84
+ for (const menuList of this.$children.MenuList) {
85
+ menuList.close();
86
+ }
93
87
  if (document.activeElement instanceof HTMLElement && this.$el.contains(document.activeElement)) {
94
88
  document.activeElement.blur();
95
89
  }
@@ -110,31 +104,32 @@ class MenuList extends Transition {
110
104
  }
111
105
  }
112
106
  /**
113
- * Update `tabindex` attribute of child focusable elements.
107
+ * Update `tabindex` attribute of children focusable elements.
114
108
  * @private
115
109
  */
116
110
  __updateTabIndexes(mode = "open") {
117
- const focusableItems = Array.from(this.$el.querySelectorAll(FOCUSABLE_ELEMENTS)).filter(
118
- (item) => this.__filterFocusableItems(item)
119
- );
120
- focusableItems.forEach((item) => {
121
- if (mode === "close") {
122
- item.setAttribute("tabindex", "-1");
123
- } else {
124
- item.removeAttribute("tabindex");
111
+ for (const item of Array.from(this.$el.querySelectorAll(FOCUSABLE_ELEMENTS))) {
112
+ if (this.__isFocusableElementFromThisMenuList(item)) {
113
+ if (mode === "close") {
114
+ item.setAttribute("tabindex", "-1");
115
+ } else {
116
+ item.removeAttribute("tabindex");
117
+ }
125
118
  }
126
- });
119
+ }
127
120
  }
128
121
  /**
129
122
  * Filter out items which are inside a child `MenuList` instance.
130
123
  * @private
131
124
  */
132
- __filterFocusableItems(item) {
125
+ __isFocusableElementFromThisMenuList(item) {
133
126
  let ancestor = item.parentElement;
134
- while (ancestor && (!ancestor.__base__ || !ancestor.__base__.has(this.constructor))) {
127
+ let maybeInstance = null;
128
+ while (!maybeInstance) {
129
+ maybeInstance = getInstanceFromElement(ancestor, this.constructor);
135
130
  ancestor = ancestor.parentElement;
136
131
  }
137
- return ancestor === null || ancestor === this.$el;
132
+ return maybeInstance === this;
138
133
  }
139
134
  }
140
135
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../packages/ui/Menu/MenuList.ts"],
4
- "sourcesContent": ["import type { BaseConfig, BaseProps } from '@studiometa/js-toolkit';\nimport { Transition } from '../Transition/index.js';\n\nconst FOCUSABLE_ELEMENTS = [\n 'a[href]:not([inert])',\n 'area[href]:not([inert])',\n 'input:not([disabled]):not([inert])',\n 'select:not([disabled]):not([inert])',\n 'textarea:not([disabled]):not([inert])',\n 'button:not([disabled]):not([inert])',\n 'iframe:not([inert])',\n 'audio:not([inert])',\n 'video:not([inert])',\n '[contenteditable]:not([inert])',\n '[tabindex]:not([inert])',\n].join(',');\n\nexport interface MenuListProps extends BaseProps {\n $children: {\n // eslint-disable-next-line no-use-before-define\n MenuList: MenuList[];\n };\n}\n\n/**\n * MenuList class.\n */\nexport class MenuList<T extends BaseProps = BaseProps> extends Transition<T & MenuListProps> {\n /**\n * Config.\n */\n static config: BaseConfig = {\n ...Transition.config,\n name: 'MenuList',\n emits: ['items-open', 'items-close', 'items-mouseleave'],\n components: {\n MenuList,\n },\n };\n\n /**\n * Are the menu items visible?\n */\n isOpen = false;\n\n /**\n * Wether the component is hovered.\n */\n isHover = false;\n\n /**\n * Override `Transition` options.\n */\n // @ts-ignore\n get $options() {\n const options = super.$options;\n\n options.leaveKeep = true;\n options.enterKeep = true;\n\n return options;\n }\n\n /**\n * Update tab indexes on mount.\n */\n mounted() {\n this.__updateTabIndexes('close');\n }\n\n /**\n * Set hover state.\n */\n onMouseenter() {\n this.isHover = true;\n }\n\n /**\n * Unset hover state.\n */\n onMouseleave() {\n this.isHover = false;\n }\n\n /**\n * Display the menu items.\n */\n open() {\n if (this.isOpen) {\n return;\n }\n\n // @todo Remove event listener when the close method is called.\n const clickOutsideHandler = (event) => {\n if (!this.$el.contains(event.target)) {\n document.removeEventListener('click', clickOutsideHandler);\n this.close();\n }\n };\n document.addEventListener('click', clickOutsideHandler);\n\n this.__updateTabIndexes('open');\n this.$el.setAttribute('aria-hidden', 'false');\n this.isOpen = true;\n this.enter();\n this.$emit('items-open');\n }\n\n /**\n * Hide the menu items.\n */\n close() {\n if (!this.isOpen) {\n return;\n }\n\n // Close child menu items.\n this.$children.MenuList.forEach((menuItem) => {\n menuItem.close();\n });\n\n if (\n document.activeElement instanceof HTMLElement &&\n this.$el.contains(document.activeElement)\n ) {\n document.activeElement.blur();\n }\n\n this.$el.setAttribute('aria-hidden', 'true');\n this.__updateTabIndexes('close');\n this.isOpen = false;\n this.leave();\n this.$emit('items-close');\n }\n\n /**\n * Toggle the menu items.\n */\n toggle() {\n if (this.isOpen) {\n this.close();\n } else {\n this.open();\n }\n }\n\n /**\n * Update `tabindex` attribute of child focusable elements.\n * @private\n */\n __updateTabIndexes(mode: 'open' | 'close' = 'open') {\n const focusableItems = Array.from(this.$el.querySelectorAll(FOCUSABLE_ELEMENTS)).filter(\n (item) => this.__filterFocusableItems(item as HTMLElement),\n );\n\n focusableItems.forEach((item) => {\n if (mode === 'close') {\n item.setAttribute('tabindex', '-1');\n } else {\n item.removeAttribute('tabindex');\n }\n });\n }\n\n /**\n * Filter out items which are inside a child `MenuList` instance.\n * @private\n */\n __filterFocusableItems(item: HTMLElement): boolean {\n let ancestor = item.parentElement;\n\n // @ts-ignore\n while (ancestor && (!ancestor.__base__ || !ancestor.__base__.has(this.constructor))) {\n ancestor = ancestor.parentElement;\n }\n\n return ancestor === null || ancestor === this.$el;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { BaseConfig, BaseProps } from '@studiometa/js-toolkit';\nimport { getInstanceFromElement } from '@studiometa/js-toolkit';\nimport { Transition } from '../Transition/index.js';\n\nconst FOCUSABLE_ELEMENTS = [\n 'a[href]:not([inert])',\n 'area[href]:not([inert])',\n 'input:not([disabled]):not([inert])',\n 'select:not([disabled]):not([inert])',\n 'textarea:not([disabled]):not([inert])',\n 'button:not([disabled]):not([inert])',\n 'iframe:not([inert])',\n 'audio:not([inert])',\n 'video:not([inert])',\n '[contenteditable]:not([inert])',\n '[tabindex]:not([inert])',\n].join(',');\n\nexport interface MenuListProps extends BaseProps {\n $children: {\n // eslint-disable-next-line no-use-before-define\n MenuList: MenuList[];\n };\n}\n\n/**\n * MenuList class.\n */\nexport class MenuList<T extends BaseProps = BaseProps> extends Transition<T & MenuListProps> {\n /**\n * Config.\n */\n static config: BaseConfig = {\n ...Transition.config,\n name: 'MenuList',\n emits: ['items-open', 'items-close', 'items-mouseleave'],\n components: {\n MenuList,\n },\n };\n\n /**\n * Are the menu items visible?\n */\n isOpen = false;\n\n /**\n * Wether the component is hovered.\n */\n isHover = false;\n\n /**\n * Override `Transition` options.\n */\n // @ts-ignore\n get $options() {\n const options = super.$options;\n\n options.leaveKeep = true;\n options.enterKeep = true;\n\n return options;\n }\n\n /**\n * Update tab indexes on mount.\n */\n mounted() {\n this.__updateTabIndexes('close');\n }\n\n /**\n * Set hover state.\n */\n onMouseenter() {\n this.isHover = true;\n }\n\n /**\n * Unset hover state.\n */\n onMouseleave() {\n this.isHover = false;\n }\n\n /**\n * Display the menu items.\n */\n open() {\n if (this.isOpen) {\n return;\n }\n\n this.__updateTabIndexes('open');\n this.$el.setAttribute('aria-hidden', 'false');\n this.isOpen = true;\n this.enter();\n this.$emit('items-open');\n }\n\n /**\n * Hide the menu items.\n */\n close() {\n if (!this.isOpen) {\n return;\n }\n\n // Close child menu items.\n for (const menuList of this.$children.MenuList) {\n menuList.close();\n }\n\n if (\n document.activeElement instanceof HTMLElement &&\n this.$el.contains(document.activeElement)\n ) {\n document.activeElement.blur();\n }\n\n this.$el.setAttribute('aria-hidden', 'true');\n this.__updateTabIndexes('close');\n this.isOpen = false;\n this.leave();\n this.$emit('items-close');\n }\n\n /**\n * Toggle the menu items.\n */\n toggle() {\n if (this.isOpen) {\n this.close();\n } else {\n this.open();\n }\n }\n\n /**\n * Update `tabindex` attribute of children focusable elements.\n * @private\n */\n __updateTabIndexes(mode: 'open' | 'close' = 'open') {\n for (const item of Array.from(this.$el.querySelectorAll(FOCUSABLE_ELEMENTS))) {\n if (this.__isFocusableElementFromThisMenuList(item as HTMLElement)) {\n if (mode === 'close') {\n item.setAttribute('tabindex', '-1');\n } else {\n item.removeAttribute('tabindex');\n }\n }\n }\n }\n\n /**\n * Filter out items which are inside a child `MenuList` instance.\n * @private\n */\n __isFocusableElementFromThisMenuList(item: HTMLElement): boolean {\n let ancestor = item.parentElement;\n let maybeInstance =null\n\n // @ts-ignore\n while (!maybeInstance) {\n maybeInstance = getInstanceFromElement(ancestor, this.constructor);\n ancestor = ancestor.parentElement;\n }\n\n return maybeInstance === this;\n }\n}\n"],
5
+ "mappings": "AACA,SAAS,8BAA8B;AACvC,SAAS,kBAAkB;AAE3B,MAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAYH,MAAM,iBAAkD,WAA8B;AAAA;AAAA;AAAA;AAAA,EAI3F,OAAO,SAAqB;AAAA,IAC1B,GAAG,WAAW;AAAA,IACd,MAAM;AAAA,IACN,OAAO,CAAC,cAAc,eAAe,kBAAkB;AAAA,IACvD,YAAY;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS;AAAA;AAAA;AAAA;AAAA,EAKT,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,EAMV,IAAI,WAAW;AACb,UAAM,UAAU,MAAM;AAEtB,YAAQ,YAAY;AACpB,YAAQ,YAAY;AAEpB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU;AACR,SAAK,mBAAmB,OAAO;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe;AACb,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe;AACb,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO;AACL,QAAI,KAAK,QAAQ;AACf;AAAA,IACF;AAEA,SAAK,mBAAmB,MAAM;AAC9B,SAAK,IAAI,aAAa,eAAe,OAAO;AAC5C,SAAK,SAAS;AACd,SAAK,MAAM;AACX,SAAK,MAAM,YAAY;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ;AACN,QAAI,CAAC,KAAK,QAAQ;AAChB;AAAA,IACF;AAGA,eAAW,YAAY,KAAK,UAAU,UAAU;AAC9C,eAAS,MAAM;AAAA,IACjB;AAEA,QACE,SAAS,yBAAyB,eAClC,KAAK,IAAI,SAAS,SAAS,aAAa,GACxC;AACA,eAAS,cAAc,KAAK;AAAA,IAC9B;AAEA,SAAK,IAAI,aAAa,eAAe,MAAM;AAC3C,SAAK,mBAAmB,OAAO;AAC/B,SAAK,SAAS;AACd,SAAK,MAAM;AACX,SAAK,MAAM,aAAa;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS;AACP,QAAI,KAAK,QAAQ;AACf,WAAK,MAAM;AAAA,IACb,OAAO;AACL,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,mBAAmB,OAAyB,QAAQ;AAClD,eAAW,QAAQ,MAAM,KAAK,KAAK,IAAI,iBAAiB,kBAAkB,CAAC,GAAG;AAC5E,UAAI,KAAK,qCAAqC,IAAmB,GAAG;AAClE,YAAI,SAAS,SAAS;AACpB,eAAK,aAAa,YAAY,IAAI;AAAA,QACpC,OAAO;AACL,eAAK,gBAAgB,UAAU;AAAA,QACjC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,qCAAqC,MAA4B;AAC/D,QAAI,WAAW,KAAK;AACpB,QAAI,gBAAe;AAGnB,WAAO,CAAC,eAAe;AACrB,sBAAgB,uBAAuB,UAAU,KAAK,WAAW;AACjE,iBAAW,SAAS;AAAA,IACtB;AAEA,WAAO,kBAAkB;AAAA,EAC3B;AACF;",
6
6
  "names": []
7
7
  }
@@ -8,6 +8,7 @@ export interface AbstractPrefetchProps extends BaseProps {
8
8
  }
9
9
  /**
10
10
  * AbstractPrefetch class.
11
+ * @see https://ui.studiometa.dev/-/components/Prefetch/
11
12
  */
12
13
  export declare class AbstractPrefetch<T extends BaseProps = BaseProps> extends Base<T & AbstractPrefetchProps> {
13
14
  /**
@@ -19,11 +20,15 @@ export declare class AbstractPrefetch<T extends BaseProps = BaseProps> extends B
19
20
  */
20
21
  static prefetchedUrls: Set<string>;
21
22
  /**
22
- * Is the given anchor prefetchable?
23
+ * Get the URL to prefetch.
23
24
  */
24
- isPrefetchable(url: URL): boolean;
25
+ get url(): URL | null;
25
26
  /**
26
- * Prefetch the given URL and terminate the component.
27
+ * Is the URL prefetchable?
27
28
  */
28
- prefetch(url: URL): void;
29
+ get isPrefetchable(): boolean;
30
+ /**
31
+ * Prefetch the URL.
32
+ */
33
+ prefetch(): void;
29
34
  }
@@ -5,6 +5,7 @@ class AbstractPrefetch extends Base {
5
5
  */
6
6
  static config = {
7
7
  name: "AbstractPrefetch",
8
+ emits: ["prefetched"],
8
9
  options: {
9
10
  prefetch: {
10
11
  type: Boolean,
@@ -17,9 +18,17 @@ class AbstractPrefetch extends Base {
17
18
  */
18
19
  static prefetchedUrls = /* @__PURE__ */ new Set();
19
20
  /**
20
- * Is the given anchor prefetchable?
21
+ * Get the URL to prefetch.
21
22
  */
22
- isPrefetchable(url) {
23
+ get url() {
24
+ const { href } = this.$el;
25
+ return href ? new URL(href) : null;
26
+ }
27
+ /**
28
+ * Is the URL prefetchable?
29
+ */
30
+ get isPrefetchable() {
31
+ const { url } = this;
23
32
  if (!url || !url.href) {
24
33
  return false;
25
34
  }
@@ -29,10 +38,7 @@ class AbstractPrefetch extends Base {
29
38
  if (url.origin !== window.location.origin) {
30
39
  return false;
31
40
  }
32
- if (!["http:", "https:"].includes(url.protocol)) {
33
- return false;
34
- }
35
- if (url.protocol === "http:" && window.location.protocol === "https:") {
41
+ if (url.href === window.location.href) {
36
42
  return false;
37
43
  }
38
44
  if (url.hash && url.pathname + url.search === window.location.pathname + window.location.search) {
@@ -41,21 +47,22 @@ class AbstractPrefetch extends Base {
41
47
  return true;
42
48
  }
43
49
  /**
44
- * Prefetch the given URL and terminate the component.
50
+ * Prefetch the URL.
45
51
  */
46
- prefetch(url) {
52
+ prefetch() {
53
+ const { url } = this;
47
54
  if (AbstractPrefetch.prefetchedUrls.has(url.href)) {
48
55
  return;
49
56
  }
50
- if (!this.isPrefetchable(url)) {
57
+ if (!this.isPrefetchable) {
51
58
  return;
52
59
  }
53
60
  const prefetcher = document.createElement("link");
54
61
  prefetcher.rel = "prefetch";
55
62
  prefetcher.href = url.href;
63
+ prefetcher.addEventListener("load", () => this.$emit("prefetched", url));
56
64
  document.head.append(prefetcher);
57
65
  AbstractPrefetch.prefetchedUrls.add(url.href);
58
- this.$terminate();
59
66
  }
60
67
  }
61
68
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../packages/ui/Prefetch/AbstractPrefetch.ts"],
4
- "sourcesContent": ["import { Base } from '@studiometa/js-toolkit';\nimport type { BaseProps, BaseConfig } from '@studiometa/js-toolkit';\n\nexport interface AbstractPrefetchProps extends BaseProps {\n $el: HTMLAnchorElement;\n $options: {\n prefetch: boolean;\n };\n}\n\n/**\n * AbstractPrefetch class.\n */\nexport class AbstractPrefetch<T extends BaseProps = BaseProps> extends Base<\n T & AbstractPrefetchProps\n> {\n /**\n * Config.\n */\n static config: BaseConfig = {\n name: 'AbstractPrefetch',\n options: {\n prefetch: {\n type: Boolean,\n default: true,\n },\n },\n };\n\n /**\n * Store prefetched URL.\n */\n static prefetchedUrls: Set<string> = new Set();\n\n /**\n * Is the given anchor prefetchable?\n */\n isPrefetchable(url: URL): boolean {\n if (!url || !url.href) {\n return false;\n }\n\n if (!this.$options.prefetch) {\n return false;\n }\n\n if (url.origin !== window.location.origin) {\n return false;\n }\n\n if (!['http:', 'https:'].includes(url.protocol)) {\n return false;\n }\n\n if (url.protocol === 'http:' && window.location.protocol === 'https:') {\n return false;\n }\n\n if (\n url.hash &&\n url.pathname + url.search === window.location.pathname + window.location.search\n ) {\n return false;\n }\n\n return true;\n }\n\n /**\n * Prefetch the given URL and terminate the component.\n */\n prefetch(url: URL) {\n if (AbstractPrefetch.prefetchedUrls.has(url.href)) {\n return;\n }\n\n if (!this.isPrefetchable(url)) {\n return;\n }\n\n const prefetcher = document.createElement('link');\n prefetcher.rel = 'prefetch';\n prefetcher.href = url.href;\n document.head.append(prefetcher);\n\n AbstractPrefetch.prefetchedUrls.add(url.href);\n\n this.$terminate();\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,YAAY;AAad,MAAM,yBAA0D,KAErE;AAAA;AAAA;AAAA;AAAA,EAIA,OAAO,SAAqB;AAAA,IAC1B,MAAM;AAAA,IACN,SAAS;AAAA,MACP,UAAU;AAAA,QACR,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,iBAA8B,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA,EAK7C,eAAe,KAAmB;AAChC,QAAI,CAAC,OAAO,CAAC,IAAI,MAAM;AACrB,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,KAAK,SAAS,UAAU;AAC3B,aAAO;AAAA,IACT;AAEA,QAAI,IAAI,WAAW,OAAO,SAAS,QAAQ;AACzC,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,CAAC,SAAS,QAAQ,EAAE,SAAS,IAAI,QAAQ,GAAG;AAC/C,aAAO;AAAA,IACT;AAEA,QAAI,IAAI,aAAa,WAAW,OAAO,SAAS,aAAa,UAAU;AACrE,aAAO;AAAA,IACT;AAEA,QACE,IAAI,QACJ,IAAI,WAAW,IAAI,WAAW,OAAO,SAAS,WAAW,OAAO,SAAS,QACzE;AACA,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,KAAU;AACjB,QAAI,iBAAiB,eAAe,IAAI,IAAI,IAAI,GAAG;AACjD;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,eAAe,GAAG,GAAG;AAC7B;AAAA,IACF;AAEA,UAAM,aAAa,SAAS,cAAc,MAAM;AAChD,eAAW,MAAM;AACjB,eAAW,OAAO,IAAI;AACtB,aAAS,KAAK,OAAO,UAAU;AAE/B,qBAAiB,eAAe,IAAI,IAAI,IAAI;AAE5C,SAAK,WAAW;AAAA,EAClB;AACF;",
4
+ "sourcesContent": ["import { Base } from '@studiometa/js-toolkit';\nimport type { BaseProps, BaseConfig } from '@studiometa/js-toolkit';\n\nexport interface AbstractPrefetchProps extends BaseProps {\n $el: HTMLAnchorElement;\n $options: {\n prefetch: boolean;\n };\n}\n\n/**\n * AbstractPrefetch class.\n * @see https://ui.studiometa.dev/-/components/Prefetch/\n */\nexport class AbstractPrefetch<T extends BaseProps = BaseProps> extends Base<\n T & AbstractPrefetchProps\n> {\n /**\n * Config.\n */\n static config: BaseConfig = {\n name: 'AbstractPrefetch',\n emits: ['prefetched'],\n options: {\n prefetch: {\n type: Boolean,\n default: true,\n },\n },\n };\n\n /**\n * Store prefetched URL.\n */\n static prefetchedUrls: Set<string> = new Set();\n\n /**\n * Get the URL to prefetch.\n */\n get url(): URL | null {\n const { href } = this.$el;\n return href ? new URL(href) : null;\n }\n\n /**\n * Is the URL prefetchable?\n */\n get isPrefetchable(): boolean {\n const { url } = this;\n\n if (!url || !url.href) {\n return false;\n }\n\n if (!this.$options.prefetch) {\n return false;\n }\n\n if (url.origin !== window.location.origin) {\n return false;\n }\n\n if (url.href === window.location.href) {\n return false;\n }\n\n if (\n url.hash &&\n url.pathname + url.search === window.location.pathname + window.location.search\n ) {\n return false;\n }\n\n return true;\n }\n\n /**\n * Prefetch the URL.\n */\n prefetch() {\n const { url } = this;\n\n if (AbstractPrefetch.prefetchedUrls.has(url.href)) {\n return;\n }\n\n if (!this.isPrefetchable) {\n return;\n }\n\n const prefetcher = document.createElement('link');\n prefetcher.rel = 'prefetch';\n prefetcher.href = url.href;\n prefetcher.addEventListener('load', () => this.$emit('prefetched', url));\n document.head.append(prefetcher);\n\n AbstractPrefetch.prefetchedUrls.add(url.href);\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,YAAY;AAcd,MAAM,yBAA0D,KAErE;AAAA;AAAA;AAAA;AAAA,EAIA,OAAO,SAAqB;AAAA,IAC1B,MAAM;AAAA,IACN,OAAO,CAAC,YAAY;AAAA,IACpB,SAAS;AAAA,MACP,UAAU;AAAA,QACR,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,iBAA8B,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA,EAK7C,IAAI,MAAkB;AACpB,UAAM,EAAE,KAAK,IAAI,KAAK;AACtB,WAAO,OAAO,IAAI,IAAI,IAAI,IAAI;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,iBAA0B;AAC5B,UAAM,EAAE,IAAI,IAAI;AAEhB,QAAI,CAAC,OAAO,CAAC,IAAI,MAAM;AACrB,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,KAAK,SAAS,UAAU;AAC3B,aAAO;AAAA,IACT;AAEA,QAAI,IAAI,WAAW,OAAO,SAAS,QAAQ;AACzC,aAAO;AAAA,IACT;AAEA,QAAI,IAAI,SAAS,OAAO,SAAS,MAAM;AACrC,aAAO;AAAA,IACT;AAEA,QACE,IAAI,QACJ,IAAI,WAAW,IAAI,WAAW,OAAO,SAAS,WAAW,OAAO,SAAS,QACzE;AACA,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW;AACT,UAAM,EAAE,IAAI,IAAI;AAEhB,QAAI,iBAAiB,eAAe,IAAI,IAAI,IAAI,GAAG;AACjD;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,gBAAgB;AACxB;AAAA,IACF;AAEA,UAAM,aAAa,SAAS,cAAc,MAAM;AAChD,eAAW,MAAM;AACjB,eAAW,OAAO,IAAI;AACtB,eAAW,iBAAiB,QAAQ,MAAM,KAAK,MAAM,cAAc,GAAG,CAAC;AACvE,aAAS,KAAK,OAAO,UAAU;AAE/B,qBAAiB,eAAe,IAAI,IAAI,IAAI;AAAA,EAC9C;AACF;",
6
6
  "names": []
7
7
  }
@@ -2,6 +2,7 @@ import type { BaseProps, BaseConfig } from '@studiometa/js-toolkit';
2
2
  import { AbstractPrefetch } from './AbstractPrefetch.js';
3
3
  /**
4
4
  * PrefetchWhenOver class.
5
+ * @see https://ui.studiometa.dev/-/components/Prefetch/
5
6
  */
6
7
  export declare class PrefetchWhenOver<T extends BaseProps = BaseProps> extends AbstractPrefetch<T> {
7
8
  /**
@@ -11,7 +11,7 @@ class PrefetchWhenOver extends AbstractPrefetch {
11
11
  * Prefetch on mouseenter.
12
12
  */
13
13
  onMouseenter() {
14
- this.prefetch(new URL(this.$el.href));
14
+ this.prefetch();
15
15
  }
16
16
  }
17
17
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../packages/ui/Prefetch/PrefetchWhenOver.ts"],
4
- "sourcesContent": ["import type { BaseProps, BaseConfig } from '@studiometa/js-toolkit';\nimport { AbstractPrefetch } from './AbstractPrefetch.js';\n\n/**\n * PrefetchWhenOver class.\n */\nexport class PrefetchWhenOver<T extends BaseProps = BaseProps> extends AbstractPrefetch<T> {\n /**\n * Config.\n */\n static config: BaseConfig = {\n ...AbstractPrefetch.config,\n name: 'PrefetchWhenOver',\n };\n\n /**\n * Prefetch on mouseenter.\n */\n onMouseenter() {\n this.prefetch(new URL(this.$el.href));\n }\n}\n"],
5
- "mappings": "AACA,SAAS,wBAAwB;AAK1B,MAAM,yBAA0D,iBAAoB;AAAA;AAAA;AAAA;AAAA,EAIzF,OAAO,SAAqB;AAAA,IAC1B,GAAG,iBAAiB;AAAA,IACpB,MAAM;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe;AACb,SAAK,SAAS,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC;AAAA,EACtC;AACF;",
4
+ "sourcesContent": ["import type { BaseProps, BaseConfig } from '@studiometa/js-toolkit';\nimport { AbstractPrefetch } from './AbstractPrefetch.js';\n\n/**\n * PrefetchWhenOver class.\n * @see https://ui.studiometa.dev/-/components/Prefetch/\n */\nexport class PrefetchWhenOver<T extends BaseProps = BaseProps> extends AbstractPrefetch<T> {\n /**\n * Config.\n */\n static config: BaseConfig = {\n ...AbstractPrefetch.config,\n name: 'PrefetchWhenOver',\n };\n\n /**\n * Prefetch on mouseenter.\n */\n onMouseenter() {\n this.prefetch();\n }\n}\n"],
5
+ "mappings": "AACA,SAAS,wBAAwB;AAM1B,MAAM,yBAA0D,iBAAoB;AAAA;AAAA;AAAA;AAAA,EAIzF,OAAO,SAAqB;AAAA,IAC1B,GAAG,iBAAiB;AAAA,IACpB,MAAM;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe;AACb,SAAK,SAAS;AAAA,EAChB;AACF;",
6
6
  "names": []
7
7
  }
@@ -3,6 +3,7 @@ import { AbstractPrefetch } from './AbstractPrefetch.js';
3
3
  declare const PrefetchWhenVisible_base: import("@studiometa/js-toolkit").BaseDecorator<import("@studiometa/js-toolkit").WithMountWhenInViewInterface, AbstractPrefetch<import("@studiometa/js-toolkit").BaseProps>, import("@studiometa/js-toolkit").WithMountWhenInViewProps>;
4
4
  /**
5
5
  * PrefetchWhenVisible class.
6
+ * @see https://ui.studiometa.dev/-/components/Prefetch/
6
7
  */
7
8
  export declare class PrefetchWhenVisible extends PrefetchWhenVisible_base {
8
9
  /**
@@ -12,7 +12,7 @@ class PrefetchWhenVisible extends withMountWhenInView(AbstractPrefetch) {
12
12
  * Prefetch on mount.
13
13
  */
14
14
  mounted() {
15
- this.prefetch(new URL(this.$el.href));
15
+ this.prefetch();
16
16
  }
17
17
  }
18
18
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../packages/ui/Prefetch/PrefetchWhenVisible.ts"],
4
- "sourcesContent": ["import { withMountWhenInView } from '@studiometa/js-toolkit';\nimport type { BaseConfig } from '@studiometa/js-toolkit';\nimport { AbstractPrefetch } from './AbstractPrefetch.js';\n\n/**\n * PrefetchWhenVisible class.\n */\nexport class PrefetchWhenVisible extends withMountWhenInView<AbstractPrefetch>(AbstractPrefetch) {\n /**\n * Config.\n */\n static config: BaseConfig = {\n ...AbstractPrefetch.config,\n name: 'PrefetchWhenVisible',\n };\n\n /**\n * Prefetch on mount.\n */\n mounted() {\n this.prefetch(new URL(this.$el.href));\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,2BAA2B;AAEpC,SAAS,wBAAwB;AAK1B,MAAM,4BAA4B,oBAAsC,gBAAgB,EAAE;AAAA;AAAA;AAAA;AAAA,EAI/F,OAAO,SAAqB;AAAA,IAC1B,GAAG,iBAAiB;AAAA,IACpB,MAAM;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU;AACR,SAAK,SAAS,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC;AAAA,EACtC;AACF;",
4
+ "sourcesContent": ["import { withMountWhenInView } from '@studiometa/js-toolkit';\nimport type { BaseConfig } from '@studiometa/js-toolkit';\nimport { AbstractPrefetch } from './AbstractPrefetch.js';\n\n/**\n * PrefetchWhenVisible class.\n * @see https://ui.studiometa.dev/-/components/Prefetch/\n */\nexport class PrefetchWhenVisible extends withMountWhenInView<AbstractPrefetch>(AbstractPrefetch) {\n /**\n * Config.\n */\n static config: BaseConfig = {\n ...AbstractPrefetch.config,\n name: 'PrefetchWhenVisible',\n };\n\n /**\n * Prefetch on mount.\n */\n mounted() {\n this.prefetch();\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,2BAA2B;AAEpC,SAAS,wBAAwB;AAM1B,MAAM,4BAA4B,oBAAsC,gBAAgB,EAAE;AAAA;AAAA;AAAA;AAAA,EAI/F,OAAO,SAAqB;AAAA,IAC1B,GAAG,iBAAiB;AAAA,IACpB,MAAM;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU;AACR,SAAK,SAAS;AAAA,EAChB;AACF;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studiometa/ui",
3
- "version": "1.0.0-rc.3",
3
+ "version": "1.0.0-rc.5",
4
4
  "description": "A set of opiniated, unstyled and accessible components",
5
5
  "publishConfig": {
6
6
  "access": "public"