Skip to content

Commit beb237a

Browse files
authored
Merge pull request #561 from umbraco/feature/popover-v2
Feature/popover-container
2 parents 2ec521a + 71db838 commit beb237a

23 files changed

+7135
-4572
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ If you want to develop a component or contribute to the repository go to ["Get s
6767
| [`<uui-modal>`](packages/uui-modal) | [![npm](https://img.shields.io/npm/v/@umbraco-ui/uui-modal?logoColor=%231B264F)](https://www.npmjs.com/package/@umbraco-ui/uui-modal) |
6868
| [`<uui-pagination>`](packages/uui-pagination) | [![npm](https://img.shields.io/npm/v/@umbraco-ui/uui-pagination?logoColor=%231B264F)](https://www.npmjs.com/package/@umbraco-ui/uui-pagination) |
6969
| [`<uui-popover>`](packages/uui-popover) | [![npm](https://img.shields.io/npm/v/@umbraco-ui/uui-popover?logoColor=%231B264F)](https://www.npmjs.com/package/@umbraco-ui/uui-popover) |
70+
| [`<uui-popover-container>`](packages/uui-popover-container) | [![npm](https://img.shields.io/npm/v/@umbraco-ui/uui-popover-container?logoColor=%231B264F)](https://www.npmjs.com/package/@umbraco-ui/uui-popover-container) |
7071
| [`<uui-progress-bar>`](packages/uui-progress-bar) | [![npm](https://img.shields.io/npm/v/@umbraco-ui/uui-progress-bar?logoColor=%231B264F)](https://www.npmjs.com/package/@umbraco-ui/uui-progress-bar) |
7172
| [`<uui-range-slider>`](packages/uui-range-slider) | [![npm](https://img.shields.io/npm/v/@umbraco-ui/uui-range-slider?logoColor=%231B264F)](https://www.npmjs.com/package/@umbraco-ui/uui-range-slider) |
7273
| [`<uui-radio>`](packages/uui-radio) | [![npm](https://img.shields.io/npm/v/@umbraco-ui/uui-radio?logoColor=%231B264F)](https://www.npmjs.com/package/@umbraco-ui/uui-radio) |

package-lock.json

Lines changed: 6337 additions & 4565 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
"babel-loader": "9.1.3",
8585
"chromatic": "6.20.0",
8686
"element-internals-polyfill": "1.3.5",
87+
"esbuild": "0.17.18",
8788
"eslint": "8.45.0",
8889
"eslint-config-prettier": "8.9.0",
8990
"eslint-import-resolver-typescript": "3.5.5",
@@ -125,8 +126,8 @@
125126
"storybook": "7.2.2",
126127
"tsc-files": "1.1.4",
127128
"turbo": "^1.10.12",
128-
"typescript": "5.0.4",
129-
"vite": "4.4.4",
129+
"typescript": "5.2.2",
130+
"vite": "4.4.9",
130131
"vite-tsconfig-paths": "4.2.0",
131132
"web-component-analyzer": "1.1.7"
132133
},

packages/rollup-package.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import importCss from 'rollup-plugin-import-css';
1010

1111
// @ts-ignore-start
1212
// eslint-disable-next-line -- // @typescript-eslint/ban-ts-comment // @ts-ignore
13-
import properties from '../packages/uui-css/custom-properties.module.js'; // eslint-disable-line
13+
import properties from './uui-css/custom-properties.module.js'; // eslint-disable-line
1414
// @ts-ignore-end
1515

1616
const esbuidOptions = { minify: true };
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { LitElement } from 'lit';
2+
import { property } from 'lit/decorators.js';
3+
import { findAncestorByAttributeValue } from '../utils';
4+
5+
type Constructor<T = {}> = new (...args: any[]) => T;
6+
7+
export declare class PopoverTargetMixinInterface {
8+
/**
9+
* Set a popovertarget.
10+
* @type {string}
11+
* @attr
12+
* @default undefined
13+
*/
14+
public popoverContainerElement?: string;
15+
16+
/**
17+
* Toggle the popover.
18+
*/
19+
protected _togglePopover(): void;
20+
}
21+
22+
/**
23+
* This mixin provides popover target functionality to other components.
24+
*
25+
* @param {Object} superClass - superclass to be extended.
26+
* @mixin
27+
*/
28+
export const PopoverTargetMixin = <T extends Constructor<LitElement>>(
29+
superClass: T
30+
) => {
31+
/**
32+
* Popover target mixin class containing the popover target functionality.
33+
*/
34+
class PopoverTargetMixinClass extends superClass {
35+
/**
36+
* Set a popovertarget.
37+
* @type {string}
38+
* @attr
39+
* @default undefined
40+
*/
41+
@property({ type: String, attribute: 'popovertarget' })
42+
public popoverContainerElement?: string;
43+
44+
#popoverIsOpen = false;
45+
46+
constructor(...args: any[]) {
47+
super(...args);
48+
this.addEventListener('uui-popover-before-toggle', this.#popoverListener);
49+
}
50+
51+
protected _togglePopover = () => {
52+
if (!this.popoverContainerElement) return;
53+
54+
const popoverContainerElement = findAncestorByAttributeValue(
55+
this,
56+
'id',
57+
this.popoverContainerElement
58+
);
59+
if (!popoverContainerElement) return;
60+
61+
this.#popoverIsOpen
62+
? // @ts-ignore - This is part of the new popover API, but typescript doesn't recognize it yet.
63+
popoverContainerElement.hidePopover()
64+
: // @ts-ignore - This is part of the new popover API, but typescript doesn't recognize it yet.
65+
popoverContainerElement.showPopover();
66+
};
67+
68+
#popoverListener = (event: any) => {
69+
// Wait for the click event to finish before updating the popover state
70+
requestAnimationFrame(() => {
71+
this.#popoverIsOpen = event.detail.newState === 'open';
72+
});
73+
};
74+
}
75+
return PopoverTargetMixinClass as unknown as Constructor<PopoverTargetMixinInterface> &
76+
T;
77+
};

packages/uui-base/lib/mixins/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export * from './LabelMixin';
33
export * from './SelectableMixin';
44
export * from './SelectOnlyMixin';
55
export * from './FormControlMixin';
6+
export * from './PopoverTargetMixin';
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Recursively find an ancestor element by attribute name and value. This also checks children of the ancestor.
3+
* @func findAncestorByAttributeValue
4+
* @param {HTMLElement} startNode - Origin node of the search.
5+
* @param {string} attributeName - Name of the attribute to search for.
6+
* @param {string} attributeValue - Value of the attribute to search for.
7+
*/
8+
export const findAncestorByAttributeValue = (
9+
startNode: HTMLElement | ParentNode,
10+
attributeName: string,
11+
attributeValue: string
12+
): HTMLElement | null => {
13+
let currentNode: typeof startNode | null = startNode;
14+
15+
while (currentNode !== null) {
16+
// Check if the current element has the specified attribute
17+
const elementHasAttribute =
18+
currentNode instanceof HTMLElement &&
19+
currentNode.hasAttribute(attributeName) &&
20+
currentNode.getAttribute(attributeName) === attributeValue;
21+
22+
// Check if the current element contains an element with the specified attribute
23+
const elementContainsAttribute =
24+
currentNode.querySelector(`[${attributeName}="${attributeValue}"]`) !==
25+
null;
26+
27+
if (elementHasAttribute) {
28+
return currentNode as HTMLElement; // Found a matching ancestor
29+
} else if (elementContainsAttribute) {
30+
return currentNode.querySelector(
31+
`[${attributeName}="${attributeValue}"]`
32+
) as HTMLElement; // Found a matching ancestor
33+
}
34+
35+
// Move up the DOM tree to the parent or parentNode, whichever is available
36+
currentNode = currentNode.parentElement || currentNode.parentNode || null;
37+
}
38+
39+
return null; // No matching ancestor found
40+
};

packages/uui-base/lib/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './Timer';
22
export * from './demandCustomElement';
33
export * from './drag';
44
export * from './math';
5+
export * from './findAncestorByAttributeValue';

packages/uui-button/lib/uui-button.element.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import {
33
UUIHorizontalShakeKeyframes,
44
} from '@umbraco-ui/uui-base/lib/animations';
55
import { demandCustomElement } from '@umbraco-ui/uui-base/lib/utils';
6-
import { FormControlMixin, LabelMixin } from '@umbraco-ui/uui-base/lib/mixins';
6+
import {
7+
FormControlMixin,
8+
LabelMixin,
9+
PopoverTargetMixin,
10+
} from '@umbraco-ui/uui-base/lib/mixins';
711
import { defineElement } from '@umbraco-ui/uui-base/lib/registration';
812
import {
913
iconCheck,
@@ -43,7 +47,7 @@ export type UUIButtonType = 'submit' | 'button' | 'reset';
4347
*/
4448
@defineElement('uui-button')
4549
export class UUIButtonElement extends FormControlMixin(
46-
LabelMixin('', LitElement)
50+
LabelMixin('', PopoverTargetMixin(LitElement))
4751
) {
4852
static styles = [
4953
UUIHorizontalShakeKeyframes,
@@ -459,6 +463,8 @@ export class UUIButtonElement extends FormControlMixin(
459463
break;
460464
}
461465
}
466+
467+
this._togglePopover();
462468
}
463469

464470
private _resetStateTimeout?: number;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# uui-popover-container
2+
3+
![npm](https://img.shields.io/npm/v/@umbraco-ui/uui-popover-container?logoColor=%231B264F)
4+
5+
Umbraco style popover-container component.
6+
7+
## Installation
8+
9+
### ES imports
10+
11+
```zsh
12+
npm i @umbraco-ui/uui-popover-container
13+
```
14+
15+
Import the registration of `<uui-popover-container>` via:
16+
17+
```javascript
18+
import '@umbraco-ui/uui-popover-container';
19+
```
20+
21+
When looking to leverage the `UUIPopoverContainerElement` base class as a type and/or for extension purposes, do so via:
22+
23+
```javascript
24+
import { UUIPopoverContainerElement } from '@umbraco-ui/uui-popover-container';
25+
```
26+
27+
## Usage
28+
29+
```html
30+
<uui-popover-container></uui-popover-container>
31+
```

0 commit comments

Comments
 (0)