element
- *
- * @customElement lion-select-rich
- * @extends {LitElement}
*/
-export class LionSelectRich extends ScopedElementsMixin(
- ChoiceGroupMixin(
- OverlayMixin(
- FormRegistrarMixin(
- InteractionStateMixin(ValidateMixin(FormControlMixin(SlotMixin(LitElement)))),
- ),
- ),
- ),
-) {
+// @ts-expect-error base constructors same return type
+export class LionSelectRich extends SlotMixin(ScopedElementsMixin(OverlayMixin(LionListbox))) {
static get scopedElements() {
return {
...super.scopedElements,
@@ -66,174 +33,109 @@ export class LionSelectRich extends ScopedElementsMixin(
static get properties() {
return {
- disabled: {
+ navigateWithinInvoker: {
type: Boolean,
- reflect: true,
+ attribute: 'navigate-within-invoker',
},
-
- readOnly: {
- type: Boolean,
- reflect: true,
- attribute: 'readonly',
- },
-
interactionMode: {
type: String,
attribute: 'interaction-mode',
},
-
- /**
- * When setting this to true, on initial render, no option will be selected.
- * It it advisable to override `_noSelectionTemplate` method in the select-invoker
- * to render some kind of placeholder initially
- */
- hasNoDefaultSelected: {
+ singleOption: {
type: Boolean,
reflect: true,
- attribute: 'has-no-default-selected',
+ attribute: 'single-option',
},
};
}
- static get styles() {
- return [
- css`
- :host {
- display: block;
- }
-
- :host([hidden]) {
- display: none;
- }
-
- :host([disabled]) {
- color: #adadad;
- }
- `,
- ];
- }
-
get slots() {
return {
...super.slots,
- invoker: () =>
- document.createElement(this.constructor.getScopedTagName('lion-select-invoker')),
+ invoker: () => document.createElement(LionSelectRich.getScopedTagName('lion-select-invoker')),
};
}
+ /** @type {LionSelectInvoker} */
get _invokerNode() {
- return Array.from(this.children).find(child => child.slot === 'invoker');
- }
-
- get _listboxNode() {
- return (
- (this._overlayCtrl && this._overlayCtrl.contentNode) ||
- Array.from(this.children).find(child => child.slot === 'input')
- );
- }
-
- get _listboxActiveDescendantNode() {
- return this._listboxNode.querySelector(`#${this._listboxActiveDescendant}`);
- }
-
- get serializedValue() {
- return this.modelValue;
- }
-
- // Duplicating from FormGroupMixin, because you cannot independently inherit/override getter + setter.
- // If you override one, gotta override the other, they go in pairs.
- set serializedValue(value) {
- super.serializedValue = value;
- }
-
- get checkedIndex() {
- let checkedIndex = -1;
- this.formElements.forEach((option, i) => {
- if (option.checked) {
- checkedIndex = i;
- }
- });
- return checkedIndex;
- }
-
- set checkedIndex(index) {
- if (this._listboxNode.children[index]) {
- this._listboxNode.children[index].checked = true;
- }
- }
-
- get activeIndex() {
- return this.formElements.findIndex(el => el.active === true);
+ return /** @type {LionSelectInvoker} */ (Array.from(this.children).find(
+ child => child.slot === 'invoker',
+ ));
}
get _scrollTargetNode() {
+ // @ts-expect-error _scrollTargetNode not on type
return this._overlayContentNode._scrollTargetNode || this._overlayContentNode;
}
- set activeIndex(index) {
- if (this.formElements[index]) {
- const el = this.formElements[index];
- el.active = true;
+ get checkedIndex() {
+ return /** @type {number} */ (super.checkedIndex);
+ }
- if (!isInView(this._scrollTargetNode, el)) {
- el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
- }
- }
+ set checkedIndex(i) {
+ super.checkedIndex = i;
}
constructor() {
super();
- this.interactionMode = 'auto';
- this.disabled = false;
- // for interaction states
- this._listboxActiveDescendant = null;
- this.__hasInitialSelectedFormElement = false;
- this.hasNoDefaultSelected = false;
- this._repropagationRole = 'choice-group'; // configures FormControlMixin
+ /**
+ * When invoker has focus, up and down arrow keys changes active state of listbox,
+ * without opening overlay.
+ * @type {Boolean}
+ */
+ this.navigateWithinInvoker = false;
+ /**
+ * Aligns behavior for 'selectionFollowFocus' and 'navigateWithinInvoker' with
+ * platform. When 'auto' (default), platform is automatically detected
+ * @type {'windows/linux'|'mac'|'auto'}
+ */
+ this.interactionMode = 'auto';
+
+ this.singleOption = false;
+
+ this.__onKeyUp = this.__onKeyUp.bind(this);
+ this.__invokerOnBlur = this.__invokerOnBlur.bind(this);
+ this.__overlayOnHide = this.__overlayOnHide.bind(this);
+ this.__overlayOnShow = this.__overlayOnShow.bind(this);
+ this.__invokerOnClick = this.__invokerOnClick.bind(this);
+ this.__overlayBeforeShow = this.__overlayBeforeShow.bind(this);
this.__focusInvokerOnLabelClick = this.__focusInvokerOnLabelClick.bind(this);
}
connectedCallback() {
- // need to do this before anything else
- this._listboxNode.registrationTarget = this;
- if (super.connectedCallback) {
- super.connectedCallback();
- }
+ super.connectedCallback();
this._invokerNode.selectedElement = this.formElements[this.checkedIndex];
this.__setupInvokerNode();
- this.__setupListboxNode();
- this.__setupEventListeners();
-
this.__toggleInvokerDisabled();
-
if (this._labelNode) {
this._labelNode.addEventListener('click', this.__focusInvokerOnLabelClick);
}
- this.registrationComplete.then(() => {
- this.__initInteractionStates();
- });
+ this.addEventListener('keyup', this.__onKeyUp);
}
disconnectedCallback() {
- if (super.disconnectedCallback) {
- super.disconnectedCallback();
- }
+ super.disconnectedCallback();
if (this._labelNode) {
- this._labelNode.removeEventListener('click', this.__toggleChecked);
+ this._labelNode.removeEventListener('click', this.__focusInvokerOnLabelClick);
}
- this._scrollTargetNode.removeEventListener('keydown', this.__overlayOnHide);
this.__teardownInvokerNode();
- this.__teardownListboxNode();
- this.__teardownEventListeners();
+ this.removeEventListener('keyup', this.__onKeyUp);
}
+ /**
+ * @param {string} name
+ * @param {unknown} oldValue
+ */
requestUpdateInternal(name, oldValue) {
super.requestUpdateInternal(name, oldValue);
if (name === 'interactionMode') {
if (this.interactionMode === 'auto') {
this.interactionMode = detectInteractionMode();
+ } else {
+ this.selectionFollowsFocus = Boolean(this.interactionMode === 'windows/linux');
+ this.navigateWithinInvoker = Boolean(this.interactionMode === 'windows/linux');
}
}
@@ -256,35 +158,21 @@ export class LionSelectRich extends ScopedElementsMixin(
this.initInteractionState();
}
- get _inputNode() {
- // In FormControl, we get direct child [slot="input"]. This doesn't work, because the overlay
- // system wraps it in [slot="_overlay-shadow-outlet"]
- return this.querySelector('[slot="input"]');
- }
-
- render() {
- return html`
- ${this._labelTemplate()} ${this._helpTextTemplate()} ${this._inputGroupTemplate()}
- ${this._feedbackTemplate()}
-
- `;
- }
-
+ /**
+ * @param {import('lit-element').PropertyValues } changedProperties
+ */
updated(changedProperties) {
super.updated(changedProperties);
if (this.formElements.length === 1) {
- this.singleOption = true;
this._invokerNode.singleOption = true;
}
if (changedProperties.has('disabled')) {
if (this.disabled) {
this._invokerNode.makeRequestToBeDisabled();
- this.__requestOptionsToBeDisabled();
} else {
this._invokerNode.retractRequestToBeDisabled();
- this.__retractRequestOptionsToBeDisabled();
}
}
@@ -299,13 +187,13 @@ export class LionSelectRich extends ScopedElementsMixin(
if (changedProperties.has('_ariaDescribedNodes')) {
this._invokerNode.setAttribute(
'aria-describedby',
- this._inputNode.getAttribute('aria-describedby'),
+ /** @type {string} */ (this._inputNode.getAttribute('aria-describedby')),
);
}
if (changedProperties.has('showsFeedbackFor')) {
// The ValidateMixin sets aria-invalid on the inputNode, but in this component we also need it on the invoker
- this._invokerNode.setAttribute('aria-invalid', this._hasFeedbackVisibleFor('error'));
+ this._invokerNode.setAttribute('aria-invalid', `${this._hasFeedbackVisibleFor('error')}`);
}
}
@@ -314,6 +202,7 @@ export class LionSelectRich extends ScopedElementsMixin(
}
}
+ /** @deprecated. use _overlayCtrl.toggle */
toggle() {
this.opened = !this.opened;
}
@@ -321,7 +210,7 @@ export class LionSelectRich extends ScopedElementsMixin(
/**
* @override
*/
- // eslint-disable-next-line
+ // eslint-disable-next-line class-methods-use-this
_inputGroupInputTemplate() {
return html`
@@ -334,66 +223,6 @@ export class LionSelectRich extends ScopedElementsMixin(
`;
}
- /**
- * Overrides FormRegistrar adding to make sure children have specific default states when added
- *
- * @override
- * @param {*} child
- * @param {Number} indexToInsertAt
- */
- addFormElement(child, indexToInsertAt) {
- super.addFormElement(child, indexToInsertAt);
-
- // we need to adjust the elements being registered
- /* eslint-disable no-param-reassign */
- child.id = child.id || `${this.localName}-option-${uuid()}`;
-
- if (this.disabled) {
- child.makeRequestToBeDisabled();
- }
-
- // the first elements checked by default
- if (
- !this.hasNoDefaultSelected &&
- !this.__hasInitialSelectedFormElement &&
- (!child.disabled || this.disabled)
- ) {
- child.active = true;
- child.checked = true;
- this.__hasInitialSelectedFormElement = true;
- }
-
- // TODO: small perf improvement could be made if logic below would be scheduled to next update,
- // so it occurs once for all options
- this.__setAttributeForAllFormElements('aria-setsize', this.formElements.length);
- this.formElements.forEach((el, idx) => {
- el.setAttribute('aria-posinset', idx + 1);
- });
-
- this.__proxyChildModelValueChanged({ target: child });
- this.resetInteractionState();
- /* eslint-enable no-param-reassign */
- }
-
- __setupEventListeners() {
- this.__onChildActiveChanged = this.__onChildActiveChanged.bind(this);
- this.__proxyChildModelValueChanged = this.__proxyChildModelValueChanged.bind(this);
- this.__onKeyUp = this.__onKeyUp.bind(this);
-
- this._listboxNode.addEventListener('active-changed', this.__onChildActiveChanged);
- this._listboxNode.addEventListener('model-value-changed', this.__proxyChildModelValueChanged);
- this.addEventListener('keyup', this.__onKeyUp);
- }
-
- __teardownEventListeners() {
- this._listboxNode.removeEventListener('active-changed', this.__onChildActiveChanged);
- this._listboxNode.removeEventListener(
- 'model-value-changed',
- this.__proxyChildModelValueChanged,
- );
- this._listboxNode.removeEventListener('keyup', this.__onKeyUp);
- }
-
__toggleInvokerDisabled() {
if (this._invokerNode) {
this._invokerNode.disabled = this.disabled;
@@ -401,34 +230,6 @@ export class LionSelectRich extends ScopedElementsMixin(
}
}
- __onChildActiveChanged({ target }) {
- if (target.active === true) {
- this.formElements.forEach(formElement => {
- if (formElement !== target) {
- // eslint-disable-next-line no-param-reassign
- formElement.active = false;
- }
- });
- this._listboxNode.setAttribute('aria-activedescendant', target.id);
- }
- }
-
- __setAttributeForAllFormElements(attribute, value) {
- this.formElements.forEach(formElement => {
- formElement.setAttribute(attribute, value);
- });
- }
-
- __proxyChildModelValueChanged(ev) {
- // We need to redispatch the model-value-changed event on 'this', so it will
- // align with FormControl.__repropagateChildrenValues method. Also, this makes
- // it act like a portal, in case the listbox is put in a modal overlay on body level.
- if (ev.stopPropagation) {
- ev.stopPropagation();
- }
- this.dispatchEvent(new CustomEvent('model-value-changed', { detail: { element: ev.target } }));
- }
-
__syncInvokerElement() {
// sync to invoker
if (this._invokerNode) {
@@ -436,141 +237,6 @@ export class LionSelectRich extends ScopedElementsMixin(
}
}
- __getNextEnabledOption(currentIndex, offset = 1) {
- for (let i = currentIndex + offset; i < this.formElements.length; i += 1) {
- if (this.formElements[i] && !this.formElements[i].disabled) {
- return i;
- }
- }
- return currentIndex;
- }
-
- __getPreviousEnabledOption(currentIndex, offset = -1) {
- for (let i = currentIndex + offset; i >= 0; i -= 1) {
- if (this.formElements[i] && !this.formElements[i].disabled) {
- return i;
- }
- }
- return currentIndex;
- }
-
- /**
- * @desc
- * Handle various keyboard controls; UP/DOWN will shift focus; SPACE selects
- * an item.
- *
- * @param ev - the keydown event object
- */
- __listboxOnKeyUp(ev) {
- if (this.disabled) {
- return;
- }
-
- const { key } = ev;
-
- switch (key) {
- case 'Escape':
- ev.preventDefault();
- this.opened = false;
- break;
- case 'Enter':
- case ' ':
- ev.preventDefault();
- if (this.interactionMode === 'mac') {
- this.checkedIndex = this.activeIndex;
- }
- this.opened = false;
- break;
- case 'ArrowUp':
- ev.preventDefault();
- this.activeIndex = this.__getPreviousEnabledOption(this.activeIndex);
- break;
- case 'ArrowDown':
- ev.preventDefault();
- this.activeIndex = this.__getNextEnabledOption(this.activeIndex);
- break;
- case 'Home':
- ev.preventDefault();
- this.activeIndex = this.__getNextEnabledOption(0, 0);
- break;
- case 'End':
- ev.preventDefault();
- this.activeIndex = this.__getPreviousEnabledOption(this.formElements.length - 1, 0);
- break;
- /* no default */
- }
-
- const keys = ['ArrowUp', 'ArrowDown', 'Home', 'End'];
- if (keys.includes(key) && this.interactionMode === 'windows/linux') {
- this.checkedIndex = this.activeIndex;
- }
- }
-
- __listboxOnKeyDown(ev) {
- if (this.disabled) {
- return;
- }
-
- const { key } = ev;
-
- switch (key) {
- case 'Tab':
- // Tab can only be caught in keydown
- ev.preventDefault();
- this.opened = false;
- break;
- /* no default */
- }
- }
-
- __onKeyUp(ev) {
- if (this.disabled) {
- return;
- }
-
- if (this.opened) {
- return;
- }
-
- const { key } = ev;
- switch (key) {
- case 'ArrowUp':
- ev.preventDefault();
-
- if (this.interactionMode === 'mac') {
- this.opened = true;
- } else {
- this.checkedIndex = this.__getPreviousEnabledOption(this.checkedIndex);
- }
- break;
- case 'ArrowDown':
- ev.preventDefault();
- if (this.interactionMode === 'mac') {
- this.opened = true;
- } else {
- this.checkedIndex = this.__getNextEnabledOption(this.checkedIndex);
- }
- break;
- /* no default */
- }
- }
-
- __requestOptionsToBeDisabled() {
- this.formElements.forEach(el => {
- if (el.makeRequestToBeDisabled) {
- el.makeRequestToBeDisabled();
- }
- });
- }
-
- __retractRequestOptionsToBeDisabled() {
- this.formElements.forEach(el => {
- if (el.retractRequestToBeDisabled) {
- el.retractRequestToBeDisabled();
- }
- });
- }
-
__setupInvokerNode() {
this._invokerNode.id = `invoker-${this._inputId}`;
this._invokerNode.setAttribute('aria-haspopup', 'listbox');
@@ -578,17 +244,19 @@ export class LionSelectRich extends ScopedElementsMixin(
this.__setupInvokerNodeEventListener();
}
+ __invokerOnClick() {
+ if (!this.disabled && !this.readOnly && !this.singleOption && !this.__blockListShow) {
+ this._overlayCtrl.toggle();
+ }
+ }
+
+ __invokerOnBlur() {
+ this.dispatchEvent(new Event('blur'));
+ }
+
__setupInvokerNodeEventListener() {
- this.__invokerOnClick = () => {
- if (!this.disabled && !this.readOnly && !this.singleOption) {
- this._overlayCtrl.toggle();
- }
- };
this._invokerNode.addEventListener('click', this.__invokerOnClick);
- this.__invokerOnBlur = () => {
- this.dispatchEvent(new Event('blur'));
- };
this._invokerNode.addEventListener('blur', this.__invokerOnBlur);
}
@@ -598,46 +266,8 @@ export class LionSelectRich extends ScopedElementsMixin(
}
/**
- * For ShadyDom the listboxNode is available right from the start so we can add those events
- * immediately.
- * For native ShadowDom the select gets render before the listboxNode is available so we
- * will add an event to the slotchange and add the events once available.
+ * @override OverlayMixin
*/
- __setupListboxNode() {
- if (this._listboxNode) {
- this.__setupListboxNodeEventListener();
- } else {
- const inputSlot = this.shadowRoot.querySelector('slot[name=input]');
- if (inputSlot) {
- inputSlot.addEventListener('slotchange', () => {
- this.__setupListboxNodeEventListener();
- });
- }
- }
- }
-
- __setupListboxNodeEventListener() {
- this.__listboxOnClick = () => {
- this.opened = false;
- };
-
- this._listboxNode.addEventListener('click', this.__listboxOnClick);
-
- this.__listboxOnKeyUp = this.__listboxOnKeyUp.bind(this);
- this._listboxNode.addEventListener('keyup', this.__listboxOnKeyUp);
-
- this.__listboxOnKeyDown = this.__listboxOnKeyDown.bind(this);
- this._listboxNode.addEventListener('keydown', this.__listboxOnKeyDown);
- }
-
- __teardownListboxNode() {
- if (this._listboxNode) {
- this._listboxNode.removeEventListener('click', this.__listboxOnClick);
- this._listboxNode.removeEventListener('keyup', this.__listboxOnKeyUp);
- this._listboxNode.removeEventListener('keydown', this.__listboxOnKeyDown);
- }
- }
-
// eslint-disable-next-line class-methods-use-this
_defineOverlayConfig() {
return {
@@ -653,36 +283,39 @@ export class LionSelectRich extends ScopedElementsMixin(
*/
_noDefaultSelectedInheritsWidth() {
if (this.checkedIndex === -1) {
- this._overlayCtrl.inheritsReferenceWidth = 'min';
+ this._overlayCtrl.updateConfig({ inheritsReferenceWidth: 'min' });
} else {
- this._overlayCtrl.inheritsReferenceWidth = this._initialInheritsReferenceWidth;
+ this._overlayCtrl.updateConfig({
+ inheritsReferenceWidth: this._initialInheritsReferenceWidth,
+ });
}
}
+ __overlayBeforeShow() {
+ if (this.hasNoDefaultSelected) {
+ this._noDefaultSelectedInheritsWidth();
+ }
+ }
+
+ __overlayOnShow() {
+ if (this.checkedIndex != null) {
+ this.activeIndex = this.checkedIndex;
+ }
+ this._listboxNode.focus();
+ }
+
+ __overlayOnHide() {
+ this._invokerNode.focus();
+ }
+
_setupOverlayCtrl() {
super._setupOverlayCtrl();
this._initialInheritsReferenceWidth = this._overlayCtrl.inheritsReferenceWidth;
- this.__overlayBeforeShow = () => {
- if (this.hasNoDefaultSelected) {
- this._noDefaultSelectedInheritsWidth();
- }
- };
- this.__overlayOnShow = () => {
- if (this.checkedIndex != null) {
- this.activeIndex = this.checkedIndex;
- }
- this._listboxNode.focus();
- };
+
this._overlayCtrl.addEventListener('before-show', this.__overlayBeforeShow);
this._overlayCtrl.addEventListener('show', this.__overlayOnShow);
- this.__overlayOnHide = () => {
- this._invokerNode.focus();
- };
this._overlayCtrl.addEventListener('hide', this.__overlayOnHide);
-
- this.__preventScrollingWithArrowKeys = this.__preventScrollingWithArrowKeys.bind(this);
- this._scrollTargetNode.addEventListener('keydown', this.__preventScrollingWithArrowKeys);
}
_teardownOverlayCtrl() {
@@ -692,21 +325,6 @@ export class LionSelectRich extends ScopedElementsMixin(
this._overlayCtrl.removeEventListener('hide', this.__overlayOnHide);
}
- __preventScrollingWithArrowKeys(ev) {
- if (this.disabled) {
- return;
- }
- const { key } = ev;
- switch (key) {
- case 'ArrowUp':
- case 'ArrowDown':
- case 'Home':
- case 'End':
- ev.preventDefault();
- /* no default */
- }
- }
-
__focusInvokerOnLabelClick() {
this._invokerNode.focus();
}
@@ -725,14 +343,103 @@ export class LionSelectRich extends ScopedElementsMixin(
return this._listboxNode;
}
- set fieldName(value) {
- this.__fieldName = value;
+ /**
+ * @param {KeyboardEvent} ev
+ */
+ __onKeyUp(ev) {
+ if (this.disabled) {
+ return;
+ }
+
+ if (this.opened) {
+ return;
+ }
+
+ const { key } = ev;
+ switch (key) {
+ case 'ArrowUp':
+ ev.preventDefault();
+
+ if (this.navigateWithinInvoker) {
+ this.setCheckedIndex(this._getPreviousEnabledOption(this.checkedIndex));
+ } else {
+ this.opened = true;
+ }
+ break;
+ case 'ArrowDown':
+ ev.preventDefault();
+ if (this.navigateWithinInvoker) {
+ this.setCheckedIndex(this._getNextEnabledOption(this.checkedIndex));
+ } else {
+ this.opened = true;
+ }
+ break;
+ /* no default */
+ }
}
- get fieldName() {
- const label =
- this.label ||
- (this.querySelector('[slot=label]') && this.querySelector('[slot=label]').textContent);
- return this.__fieldName || label || this.name;
+ /**
+ * @desc
+ * Handle various keyboard controls; UP/DOWN will shift focus; SPACE selects
+ * an item.
+ *
+ * @param {KeyboardEvent} ev - the keydown event object
+ */
+ _listboxOnKeyDown(ev) {
+ super._listboxOnKeyDown(ev);
+
+ if (this.disabled) {
+ return;
+ }
+ const { key } = ev;
+
+ switch (key) {
+ case 'Tab':
+ // Tab can only be caught in keydown
+ this.opened = false;
+ break;
+ /* no default */
+ case 'Escape':
+ this.opened = false;
+ this.__blockListShowDuringTransition();
+ break;
+ case 'Enter':
+ case ' ':
+ this.opened = false;
+ this.__blockListShowDuringTransition();
+ break;
+ /* no default */
+ }
+ }
+
+ _listboxOnClick = () => {
+ this.opened = false;
+ };
+
+ _setupListboxNodeInteractions() {
+ super._setupListboxNodeInteractions();
+ this._listboxNode.addEventListener('click', this._listboxOnClick);
+ }
+
+ _teardownListboxNode() {
+ super._teardownListboxNode();
+ if (this._listboxNode) {
+ this._listboxNode.removeEventListener('click', this._listboxOnClick);
+ }
+ }
+
+ /**
+ * Normally, when textbox gets focus or a char is typed, it opens listbox.
+ * In transition phases (like clicking option) we prevent this.
+ */
+ __blockListShowDuringTransition() {
+ this.__blockListShow = true;
+ // We need this timeout to make sure click handler triggered by keyup (space/enter) of
+ // button has been executed.
+ // TODO: alternative would be to let the 'checking' party 'release' this boolean
+ // Or: call 'stopPropagation' on keyup of keys that have been handled in keydown
+ setTimeout(() => {
+ this.__blockListShow = false;
+ }, 200);
}
}
diff --git a/packages/select-rich/src/differentKeyNamesShimIE.js b/packages/select-rich/src/differentKeyNamesShimIE.js
deleted file mode 100644
index d03a623c6..000000000
--- a/packages/select-rich/src/differentKeyNamesShimIE.js
+++ /dev/null
@@ -1,33 +0,0 @@
-const event = KeyboardEvent.prototype;
-const descriptor = Object.getOwnPropertyDescriptor(event, 'key');
-if (descriptor) {
- const keys = {
- Win: 'Meta',
- Scroll: 'ScrollLock',
- Spacebar: ' ',
-
- Down: 'ArrowDown',
- Left: 'ArrowLeft',
- Right: 'ArrowRight',
- Up: 'ArrowUp',
-
- Del: 'Delete',
- Apps: 'ContextMenu',
- Esc: 'Escape',
-
- Multiply: '*',
- Add: '+',
- Subtract: '-',
- Decimal: '.',
- Divide: '/',
- };
- Object.defineProperty(event, 'key', {
- // eslint-disable-next-line object-shorthand, func-names
- get: function () {
- const key = descriptor.get.call(this);
-
- // eslint-disable-next-line no-prototype-builtins
- return keys.hasOwnProperty(key) ? keys[key] : key;
- },
- });
-}
diff --git a/packages/select-rich/test/keyboardEventShimIE.js b/packages/select-rich/test/keyboardEventShimIE.js
deleted file mode 100644
index 45e834217..000000000
--- a/packages/select-rich/test/keyboardEventShimIE.js
+++ /dev/null
@@ -1,49 +0,0 @@
-if (typeof window.KeyboardEvent !== 'function') {
- // e.g. is IE and needs "polyfill"
- const KeyboardEvent = (event, _params) => {
- // current spec for it https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent
- const params = {
- bubbles: false,
- cancelable: false,
- view: document.defaultView,
- key: false,
- location: false,
- ctrlKey: false,
- shiftKey: false,
- altKey: false,
- metaKey: false,
- repeat: false,
- ..._params,
- };
- const modifiersListArray = [];
- if (params.ctrlKey) {
- modifiersListArray.push('Control');
- }
- if (params.shiftKey) {
- modifiersListArray.push('Shift');
- }
- if (params.altKey) {
- modifiersListArray.push('Alt');
- }
- if (params.metaKey) {
- modifiersListArray.push('Meta');
- }
-
- const ev = document.createEvent('KeyboardEvent');
- // IE Spec for it https://technet.microsoft.com/en-us/windows/ff975297(v=vs.60)
- ev.initKeyboardEvent(
- event,
- params.bubbles,
- params.cancelable,
- params.view,
- params.key,
- params.location,
- modifiersListArray.join(' '),
- params.repeat ? 1 : 0,
- params.locale,
- );
- return ev;
- };
- KeyboardEvent.prototype = window.Event.prototype;
- window.KeyboardEvent = KeyboardEvent;
-}
diff --git a/packages/select-rich/test/lion-select-listbox-suite-integration.test.js b/packages/select-rich/test/lion-select-listbox-suite-integration.test.js
new file mode 100644
index 000000000..f7c818c4b
--- /dev/null
+++ b/packages/select-rich/test/lion-select-listbox-suite-integration.test.js
@@ -0,0 +1,6 @@
+import { runListboxMixinSuite } from '@lion/listbox/test-suites/ListboxMixin.suite.js';
+import '../lion-select-rich.js';
+
+describe(' integration with ListboxMixin', () => {
+ runListboxMixinSuite({ tagString: 'lion-select-rich' });
+});
diff --git a/packages/select-rich/test/lion-select-rich-dialog-integration.test.js b/packages/select-rich/test/lion-select-rich-dialog-integration.test.js
index a055cc406..c65814a43 100644
--- a/packages/select-rich/test/lion-select-rich-dialog-integration.test.js
+++ b/packages/select-rich/test/lion-select-rich-dialog-integration.test.js
@@ -1,9 +1,9 @@
import { OverlayMixin } from '@lion/overlays';
import { LitElement } from 'lit-element';
import { defineCE, fixture, html, expect, unsafeStatic } from '@open-wc/testing';
+import '@lion/listbox/lion-option.js';
+import '@lion/listbox/lion-options.js';
import '../lion-select-rich.js';
-import '../lion-options.js';
-import '../lion-option.js';
const tagString = defineCE(
class extends OverlayMixin(LitElement) {
diff --git a/packages/select-rich/test/lion-select-rich-interaction.test.js b/packages/select-rich/test/lion-select-rich-interaction.test.js
index 68f317abf..17456f300 100644
--- a/packages/select-rich/test/lion-select-rich-interaction.test.js
+++ b/packages/select-rich/test/lion-select-rich-interaction.test.js
@@ -1,105 +1,13 @@
import { Required } from '@lion/form-core';
import { expect, html, triggerBlurFor, triggerFocusFor, fixture } from '@open-wc/testing';
-import '../lion-option.js';
-import '../lion-options.js';
+import '@lion/core/src/differentKeyEventNamesShimIE.js';
+import '@lion/listbox/lion-option.js';
+import '@lion/listbox/lion-options.js';
import '../lion-select-rich.js';
-import './keyboardEventShimIE.js';
describe('lion-select-rich interactions', () => {
- describe('values', () => {
- it('registers options', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
- expect(el.formElements.length).to.equal(2);
- expect(el.formElements).to.eql([
- el.querySelectorAll('lion-option')[0],
- el.querySelectorAll('lion-option')[1],
- ]);
- });
-
- it('has the first element by default checked and active', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
-
- expect(el.querySelector('lion-option').checked).to.be.true;
- expect(el.querySelector('lion-option').active).to.be.true;
- expect(el.modelValue).to.equal(10);
-
- expect(el.checkedIndex).to.equal(0);
- expect(el.activeIndex).to.equal(0);
- });
-
- it('allows null choiceValue', async () => {
- const el = await fixture(html`
-
-
- Please select value
- Item 2
-
-
- `);
- expect(el.modelValue).to.be.null;
- });
-
- it('has the checked option as modelValue', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
- expect(el.modelValue).to.equal(20);
- });
-
- it('has an activeIndex', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
- expect(el.activeIndex).to.equal(0);
-
- el.querySelectorAll('lion-option')[1].active = true;
- expect(el.querySelectorAll('lion-option')[0].active).to.be.false;
- expect(el.activeIndex).to.equal(1);
- });
- });
-
describe('Keyboard navigation', () => {
- it('does not allow to navigate above the first or below the last option', async () => {
- const el = await fixture(html`
-
-
- Item 1
-
-
- `);
- expect(() => {
- el.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowUp' }));
- el.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowDown' }));
- }).to.not.throw();
- expect(el.checkedIndex).to.equal(0);
- expect(el.activeIndex).to.equal(0);
- });
-
it('navigates to first and last option with [Home] and [End] keys', async () => {
const el = await fixture(html`
@@ -113,44 +21,12 @@ describe('lion-select-rich interactions', () => {
`);
expect(el.modelValue).to.equal(30);
- el._listboxNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'Home' }));
+ el._listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Home' }));
expect(el.modelValue).to.equal(10);
- el._listboxNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'End' }));
+ el._listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'End' }));
expect(el.modelValue).to.equal(40);
});
-
- // TODO: nice to have
- it.skip('selects a value with single [character] key', async () => {
- const el = await fixture(html`
-
-
- A
- B
- C
-
-
- `);
- expect(el.choiceValue).to.equal('a');
- el.dispatchEvent(new KeyboardEvent('keyup', { key: 'C' }));
- expect(el.choiceValue).to.equal('c');
- });
-
- it.skip('selects a value with multiple [character] keys', async () => {
- const el = await fixture(html`
-
-
- Bar
- Far
- Foo
-
-
- `);
- el.dispatchEvent(new KeyboardEvent('keyup', { key: 'F' }));
- expect(el.choiceValue).to.equal('far');
- el.dispatchEvent(new KeyboardEvent('keyup', { key: 'O' }));
- expect(el.choiceValue).to.equal('foo');
- });
});
describe('Keyboard navigation Windows', () => {
@@ -180,12 +56,12 @@ describe('lion-select-rich interactions', () => {
expect(el.checkedIndex).to.equal(0);
expectOnlyGivenOneOptionToBeChecked(options, 0);
- el._listboxNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowDown' }));
+ el._listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
expect(el.activeIndex).to.equal(1);
expect(el.checkedIndex).to.equal(1);
expectOnlyGivenOneOptionToBeChecked(options, 1);
- el._listboxNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowUp' }));
+ el._listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' }));
expect(el.activeIndex).to.equal(0);
expect(el.checkedIndex).to.equal(0);
expectOnlyGivenOneOptionToBeChecked(options, 0);
@@ -226,30 +102,6 @@ describe('lion-select-rich interactions', () => {
});
});
- describe('Keyboard navigation Mac', () => {
- it('navigates through open list with [ArrowDown] [ArrowUp] keys activates the option', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
- Item 3
-
-
- `);
- expect(el.activeIndex).to.equal(0);
- expect(el.checkedIndex).to.equal(0);
-
- el._listboxNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowDown' }));
- expect(el.activeIndex).to.equal(1);
- expect(el.checkedIndex).to.equal(0);
-
- el._listboxNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowUp' }));
- expect(el.activeIndex).to.equal(0);
- expect(el.checkedIndex).to.equal(0);
- });
- });
-
describe('Disabled', () => {
it('cannot be focused if disabled', async () => {
const el = await fixture(html`
@@ -260,32 +112,6 @@ describe('lion-select-rich interactions', () => {
expect(el._invokerNode.tabIndex).to.equal(-1);
});
- it('still has a checked value', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
-
- expect(el.modelValue).to.equal(10);
- });
-
- it('cannot be navigated with keyboard if disabled', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
- el._listboxNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowDown' }));
- expect(el.modelValue).to.equal(10);
- });
-
it('cannot be opened via click if disabled', async () => {
const el = await fixture(html`
@@ -307,194 +133,9 @@ describe('lion-select-rich interactions', () => {
await el.updateComplete;
expect(el._invokerNode.hasAttribute('disabled')).to.be.false;
});
-
- it('skips disabled options while navigating through list with [ArrowDown] [ArrowUp] keys', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
- Item 3
-
-
- `);
- el._listboxNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowDown' }));
- expect(el.activeIndex).to.equal(2);
-
- el._listboxNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowUp' }));
- expect(el.activeIndex).to.equal(0);
- });
-
- // flaky test
- it.skip('skips disabled options while navigates to first and last option with [Home] and [End] keys', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
- Item 3
- Item 4
-
-
- `);
- expect(el.activeIndex).to.equal(2);
-
- el._listboxNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'End' }));
- expect(el.activeIndex).to.equal(2);
-
- el._listboxNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'Home' }));
- expect(el.activeIndex).to.equal(1);
- });
-
- it('checks the first enabled option', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
- Item 3
-
-
- `);
- expect(el.activeIndex).to.equal(1);
- expect(el.checkedIndex).to.equal(1);
- });
-
- it('sync its disabled state to all options', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
- const options = [...el.querySelectorAll('lion-option')];
- el.disabled = true;
- await el.updateComplete;
- expect(options[0].disabled).to.be.true;
- expect(options[1].disabled).to.be.true;
-
- el.disabled = false;
- await el.updateComplete;
- expect(options[0].disabled).to.be.true;
- expect(options[1].disabled).to.be.false;
- });
-
- it('can be enabled (incl. its options) even if it starts as disabled', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
- const options = [...el.querySelectorAll('lion-option')];
- expect(options[0].disabled).to.be.true;
- expect(options[1].disabled).to.be.true;
-
- el.disabled = false;
- await el.updateComplete;
- expect(options[0].disabled).to.be.true;
- expect(options[1].disabled).to.be.false;
- });
- });
-
- describe('Programmatic interaction', () => {
- it('can set active state', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
- const opt = el.querySelectorAll('lion-option')[1];
- opt.active = true;
- expect(el._listboxNode.getAttribute('aria-activedescendant')).to.equal('myId');
- });
-
- it('can set checked state', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
- const option = el.querySelectorAll('lion-option')[1];
- option.checked = true;
- expect(el.modelValue).to.equal(20);
- });
-
- it('does not allow to set checkedIndex or activeIndex to be out of bound', async () => {
- const el = await fixture(html`
-
-
- Item 1
-
-
- `);
- expect(() => {
- el.activeIndex = -1;
- el.activeIndex = 1;
- el.checkedIndex = -1;
- el.checkedIndex = 1;
- }).to.not.throw();
- expect(el.checkedIndex).to.equal(0);
- expect(el.activeIndex).to.equal(0);
- });
-
- it('unsets checked on other options when option becomes checked', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
- const options = el.querySelectorAll('lion-option');
- expect(options[0].checked).to.be.true;
- options[1].checked = true;
- expect(options[0].checked).to.be.false;
- });
-
- it('unsets active on other options when option becomes active', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
- const options = el.querySelectorAll('lion-option');
- expect(options[0].active).to.be.true;
- options[1].active = true;
- expect(options[0].active).to.be.false;
- });
});
describe('Interaction states', () => {
- it('becomes dirty if value changed once', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
-
- expect(el.dirty).to.be.false;
- el.modelValue = 20;
- expect(el.dirty).to.be.true;
- });
-
it('becomes touched if blurred once', async () => {
const el = await fixture(html`
@@ -509,114 +150,9 @@ describe('lion-select-rich interactions', () => {
await triggerBlurFor(el._invokerNode);
expect(el.touched).to.be.true;
});
-
- it('is prefilled if there is a value on init', async () => {
- const el = await fixture(html`
-
-
- Item 1
-
-
- `);
- expect(el.prefilled).to.be.true;
-
- const elEmpty = await fixture(html`
-
-
- Please select a value
- Item 1
-
-
- `);
- expect(elEmpty.prefilled).to.be.false;
- });
- });
-
- describe('Validation', () => {
- it('can be required', async () => {
- const el = await fixture(html`
-
-
- Please select a value
- Item 2
-
-
- `);
-
- expect(el.hasFeedbackFor).to.include('error');
- expect(el.validationStates).to.have.a.property('error');
- expect(el.validationStates.error).to.have.a.property('Required');
-
- el.modelValue = 20;
- expect(el.hasFeedbackFor).not.to.include('error');
- expect(el.validationStates).to.have.a.property('error');
- expect(el.validationStates.error).not.to.have.a.property('Required');
- });
});
describe('Accessibility', () => {
- it('creates unique ids for all children', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
- Item 3
-
-
- `);
- expect(el.querySelectorAll('lion-option')[0].id).to.exist;
- expect(el.querySelectorAll('lion-option')[1].id).to.exist;
- expect(el.querySelectorAll('lion-option')[2].id).to.equal('predefined');
- });
-
- it('has a reference to the selected option', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
-
- expect(el._listboxNode.getAttribute('aria-activedescendant')).to.equal('first');
- el._listboxNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowDown' }));
- expect(el._listboxNode.getAttribute('aria-activedescendant')).to.equal('second');
- });
-
- it('puts "aria-setsize" on all options to indicate the total amount of options', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
- Item 3
-
-
- `);
- const optionEls = [].slice.call(el.querySelectorAll('lion-option'));
- optionEls.forEach(optionEl => {
- expect(optionEl.getAttribute('aria-setsize')).to.equal('3');
- });
- });
-
- it('puts "aria-posinset" on all options to indicate their position in the listbox', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
- Item 3
-
-
- `);
- const optionEls = [].slice.call(el.querySelectorAll('lion-option'));
- optionEls.forEach((oEl, i) => {
- expect(oEl.getAttribute('aria-posinset')).to.equal(`${i + 1}`);
- });
- });
-
it('sets [aria-invalid="true"] to "._invokerNode" when there is an error', async () => {
const el = await fixture(html`
diff --git a/packages/select-rich/test/lion-select-rich.test.js b/packages/select-rich/test/lion-select-rich.test.js
index 74d1729f7..ce10a1273 100644
--- a/packages/select-rich/test/lion-select-rich.test.js
+++ b/packages/select-rich/test/lion-select-rich.test.js
@@ -1,6 +1,5 @@
import { LitElement } from '@lion/core';
import { OverlayController } from '@lion/overlays';
-import { Required } from '@lion/form-core';
import {
aTimeout,
defineCE,
@@ -11,111 +10,13 @@ import {
fixture,
} from '@open-wc/testing';
import { LionSelectInvoker, LionSelectRich } from '../index.js';
-import '../lion-option.js';
-import '../lion-options.js';
+
+import '@lion/core/src/differentKeyEventNamesShimIE.js';
+import '@lion/listbox/lion-option.js';
+import '@lion/listbox/lion-options.js';
import '../lion-select-rich.js';
-import './keyboardEventShimIE.js';
describe('lion-select-rich', () => {
- it('has a single modelValue representing the currently checked option', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
-
- expect(el.modelValue).to.equal(10);
- });
-
- it('automatically sets the name attribute of child checkboxes to its own name', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
-
- expect(el.formElements[0].name).to.equal('foo');
- expect(el.formElements[1].name).to.equal('foo');
-
- const validChild = await fixture(html` Item 3 `);
- el.appendChild(validChild);
-
- expect(el.formElements[2].name).to.equal('foo');
- });
-
- it('throws if a child element without a modelValue like { value: "foo", checked: false } tries to register', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
- const invalidChild = await fixture(html` `);
-
- expect(() => {
- el.addFormElement(invalidChild);
- }).to.throw(
- 'The lion-select-rich name="foo" does not allow to register lion-option with .modelValue="Lara" - The modelValue should represent an Object { value: "foo", checked: false }',
- );
- });
-
- it('throws if a child element with a different name than the group tries to register', async () => {
- const el = await fixture(html`
-
-
-
-
-
-
- `);
- const invalidChild = await fixture(html`
-
- `);
-
- expect(() => {
- el.addFormElement(invalidChild);
- }).to.throw(
- 'The lion-select-rich name="gender" does not allow to register lion-option with custom names (name="foo" given)',
- );
- });
-
- it('can set initial modelValue on creation', async () => {
- const el = await fixture(html`
-
-
-
-
-
-
-
- `);
-
- expect(el.modelValue).to.equal('other');
- expect(el.formElements[2].checked).to.be.true;
- });
-
- it(`has a fieldName based on the label`, async () => {
- const el1 = await fixture(html`
-
- `);
- expect(el1.fieldName).to.equal(el1._labelNode.textContent);
-
- const el2 = await fixture(html`
-
- bar
-
- `);
- expect(el2.fieldName).to.equal(el2._labelNode.textContent);
- });
-
it('clicking the label should focus the invoker', async () => {
const el = await fixture(html`
@@ -127,119 +28,6 @@ describe('lion-select-rich', () => {
expect(document.activeElement === el._invokerNode).to.be.true;
});
- it(`has a fieldName based on the name if no label exists`, async () => {
- const el = await fixture(html`
-
- `);
- expect(el.fieldName).to.equal(el.name);
- });
-
- it(`can override fieldName`, async () => {
- const el = await fixture(html`
-
- `);
- expect(el.__fieldName).to.equal(el.fieldName);
- });
-
- it('does not have a tabindex', async () => {
- const el = await fixture(html`
-
-
-
- `);
- expect(el.hasAttribute('tabindex')).to.be.false;
- });
-
- it('delegates the name attribute to its children options', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
-
- const optOne = el.querySelectorAll('lion-option')[0];
- const optTwo = el.querySelectorAll('lion-option')[1];
-
- expect(optOne.name).to.equal('foo');
- expect(optTwo.name).to.equal('foo');
- });
-
- it('supports validation', async () => {
- const el = await fixture(html`
-
-
- select a color
- Red
- Hotpink
- Teal
-
-
- `);
-
- expect(el.hasFeedbackFor.includes('error')).to.be.true;
- expect(el.showsFeedbackFor.includes('error')).to.be.false;
-
- // test submitted prop explicitly, since we dont extend field, we add the prop manually
- el.submitted = true;
- await el.updateComplete;
- expect(el.showsFeedbackFor.includes('error')).to.be.true;
-
- el._listboxNode.children[1].checked = true;
- await el.updateComplete;
- expect(el.hasFeedbackFor.includes('error')).to.be.false;
- expect(el.showsFeedbackFor.includes('error')).to.be.false;
-
- el._listboxNode.children[0].checked = true;
- await el.updateComplete;
- expect(el.hasFeedbackFor.includes('error')).to.be.true;
- expect(el.showsFeedbackFor.includes('error')).to.be.true;
- });
-
- it('supports having no default selection initially', async () => {
- const el = await fixture(html`
-
-
- Red
- Hotpink
- Teal
-
-
- `);
-
- expect(el.selectedElement).to.be.undefined;
- expect(el.modelValue).to.equal('');
- });
-
- it('supports changing the selection through serializedValue setter', async () => {
- const el = await fixture(html`
-
-
- Red
- Hotpink
- Teal
-
-
- `);
-
- expect(el.checkedIndex).to.equal(0);
- expect(el.serializedValue).to.equal('red');
-
- el.serializedValue = 'hotpink';
-
- expect(el.checkedIndex).to.equal(1);
- expect(el.serializedValue).to.equal('hotpink');
- });
-
describe('Invoker', () => {
it('generates an lion-select-invoker if no invoker is provided', async () => {
const el = await fixture(html`
@@ -454,7 +242,7 @@ describe('lion-select-rich', () => {
`);
// The default is min, so we override that behavior here
- el._overlayCtrl.inheritsReferenceWidth = 'full';
+ el._overlayCtrl.updateConfig({ inheritsReferenceWidth: 'full' });
el._initialInheritsReferenceWidth = 'full';
expect(el._overlayCtrl.inheritsReferenceWidth).to.equal('full');
@@ -486,7 +274,7 @@ describe('lion-select-rich', () => {
elSingleoption._invokerNode.click();
await elSingleoption.updateComplete;
- expect(elSingleoption.singleOption).to.be.undefined;
+ expect(elSingleoption.singleOption).to.be.false;
const optionELm = elSingleoption.querySelectorAll('lion-option')[0];
optionELm.parentNode.removeChild(optionELm);
@@ -538,7 +326,7 @@ describe('lion-select-rich', () => {
`);
- el._listboxNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape' }));
+ el._listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
expect(el.opened).to.be.false;
});
@@ -588,7 +376,7 @@ describe('lion-select-rich', () => {
`);
- el._listboxNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }));
+ el._listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
expect(el.opened).to.be.false;
});
});
@@ -608,7 +396,7 @@ describe('lion-select-rich', () => {
el.activeIndex = 1;
expect(el.checkedIndex).to.equal(0);
- el._listboxNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }));
+ el._listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
expect(el.opened).to.be.false;
expect(el.checkedIndex).to.equal(1);
});
@@ -667,71 +455,9 @@ describe('lion-select-rich', () => {
expect(el._invokerNode.getAttribute('aria-expanded')).to.equal('true');
});
-
- it('is accessible when closed', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
- await expect(el).to.be.accessible();
- });
-
- it('is accessible when opened', async () => {
- const el = await fixture(html`
-
-
- Item 1
- Item 2
-
-
- `);
- el.opened = true;
- await el.updateComplete;
- await el.updateComplete; // need 2 awaits as overlay.show is an async function
-
- await expect(el).to.be.accessible();
- });
});
describe('Use cases', () => {
- it('works for complex array data', async () => {
- const objs = [
- { type: 'mastercard', label: 'Master Card', amount: 12000, active: true },
- { type: 'visacard', label: 'Visa Card', amount: 0, active: false },
- ];
- const el = await fixture(html`
-
-
- ${objs.map(
- obj => html`
- ${obj.label}
- `,
- )}
-
-
- `);
- expect(el.modelValue).to.deep.equal({
- type: 'mastercard',
- label: 'Master Card',
- amount: 12000,
- active: true,
- });
-
- el.checkedIndex = 1;
- expect(el.modelValue).to.deep.equal({
- type: 'visacard',
- label: 'Visa Card',
- amount: 0,
- active: false,
- });
- });
-
it('keeps showing the selected item after a new item has been added in the selectedIndex position', async () => {
const mySelectContainerTagString = defineCE(
class extends LitElement {
@@ -880,24 +606,4 @@ describe('lion-select-rich', () => {
expect(el.modelValue).to.equal('');
});
});
-
- describe('Instantiation methods', () => {
- it('can be instantiated via "document.createElement"', async () => {
- let properlyInstantiated = false;
-
- try {
- const el = document.createElement('lion-select-rich');
- const optionsEl = document.createElement('lion-options');
- optionsEl.slot = 'input';
- const optionEl = document.createElement('lion-option');
- optionsEl.appendChild(optionEl);
- el.appendChild(optionsEl);
- properlyInstantiated = true;
- } catch (e) {
- throw Error(e);
- }
-
- expect(properlyInstantiated).to.be.true;
- });
- });
});
diff --git a/tsconfig.json b/tsconfig.json
index d366667b5..07496df74 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -22,7 +22,8 @@
"packages/form-core/**/*.js",
"packages/overlays/**/*.js",
"packages/tooltip/**/*.js",
- "packages/button/src/**/*.js"
+ "packages/button/src/**/*.js",
+ "packages/listbox/src/*.js"
],
"exclude": [
"node_modules",