diff --git a/.changeset/eighty-kings-hear.md b/.changeset/eighty-kings-hear.md new file mode 100644 index 000000000..e9d9e9035 --- /dev/null +++ b/.changeset/eighty-kings-hear.md @@ -0,0 +1,36 @@ +--- +'@lion/accordion': patch +'@lion/ajax': patch +'@lion/button': patch +'@lion/calendar': patch +'@lion/checkbox-group': patch +'@lion/collapsible': patch +'@lion/combobox': patch +'@lion/core': patch +'@lion/dialog': patch +'@lion/fieldset': patch +'@lion/form': patch +'@lion/form-core': patch +'@lion/form-integrations': patch +'@lion/icon': patch +'@lion/input': patch +'@lion/input-amount': patch +'@lion/input-datepicker': patch +'@lion/input-range': patch +'@lion/input-stepper': patch +'@lion/listbox': patch +'@lion/localize': patch +'@lion/overlays': patch +'@lion/pagination': patch +'@lion/progress-indicator': patch +'@lion/select': patch +'@lion/select-rich': patch +'singleton-manager': patch +'@lion/switch': patch +'@lion/tabs': patch +'@lion/textarea': patch +'@lion/tooltip': patch +'@lion/validate-messages': patch +--- + +add protected and private type info diff --git a/packages/accordion/src/LionAccordion.js b/packages/accordion/src/LionAccordion.js index a2a74c190..c28c32f3e 100644 --- a/packages/accordion/src/LionAccordion.js +++ b/packages/accordion/src/LionAccordion.js @@ -184,10 +184,16 @@ export class LionAccordion extends LitElement { super(); this.styles = {}; - /** @type {StoreEntry[]} */ + /** + * @type {StoreEntry[]} + * @private + */ this.__store = []; - /** @type {number} */ + /** + * @type {number} + * @private + */ this.__focusedIndex = -1; /** @type {number[]} */ @@ -200,6 +206,9 @@ export class LionAccordion extends LitElement { this.__setupSlots(); } + /** + * @private + */ __setupSlots() { const invokerSlot = this.shadowRoot?.querySelector('slot[name=invoker]'); const handleSlotChange = () => { @@ -213,6 +222,9 @@ export class LionAccordion extends LitElement { } } + /** + * @private + */ __setupStore() { const invokers = /** @type {HTMLElement[]} */ (Array.from( this.querySelectorAll('[slot="invoker"]'), @@ -248,6 +260,9 @@ export class LionAccordion extends LitElement { }); } + /** + * @private + */ __cleanStore() { if (!this.__store) { return; @@ -261,6 +276,7 @@ export class LionAccordion extends LitElement { /** * * @param {number} index + * @private */ __createInvokerClickHandler(index) { return () => { @@ -271,6 +287,7 @@ export class LionAccordion extends LitElement { /** * @param {Event} e + * @private */ __handleInvokerKeydown(e) { const _e = /** @type {KeyboardEvent} */ (e); @@ -313,6 +330,9 @@ export class LionAccordion extends LitElement { return this.__focusedIndex; } + /** + * @private + */ get _pairCount() { return this.__store.length; } @@ -329,6 +349,9 @@ export class LionAccordion extends LitElement { return this.__expanded; } + /** + * @private + */ __updateFocused() { if (!(this.__store && this.__store[this.focusedIndex])) { return; @@ -345,6 +368,9 @@ export class LionAccordion extends LitElement { } } + /** + * @private + */ __updateExpanded() { if (!this.__store) { return; @@ -364,6 +390,7 @@ export class LionAccordion extends LitElement { /** * @param {number} value + * @private */ __toggleExpanded(value) { const { expanded } = this; diff --git a/packages/ajax/src/AjaxClient.js b/packages/ajax/src/AjaxClient.js index 37b72a2c8..f932ee50c 100644 --- a/packages/ajax/src/AjaxClient.js +++ b/packages/ajax/src/AjaxClient.js @@ -18,7 +18,10 @@ export class AjaxClient { * @param {Partial} config */ constructor(config = {}) { - /** @type {Partial} */ + /** + * @type {Partial} + * @private + */ this.__config = { addAcceptLanguage: true, xsrfCookieName: 'XSRF-TOKEN', diff --git a/packages/ajax/src/interceptors-cache.js b/packages/ajax/src/interceptors-cache.js index bf44bb32a..89fbad1ff 100644 --- a/packages/ajax/src/interceptors-cache.js +++ b/packages/ajax/src/interceptors-cache.js @@ -17,6 +17,7 @@ class Cache { /** * @type {{[url: string]: {expires: number, data: object} }} + * @protected */ this._cacheObject = {}; } @@ -92,6 +93,7 @@ class Cache { * Validate cache on each call to the Cache * When the expiration date has passed, the _cacheObject will be replaced by an * empty object + * @protected */ _validateCache() { if (new Date().getTime() > this.expiration) { diff --git a/packages/button/src/LionButton.js b/packages/button/src/LionButton.js index 0219b969b..7abe844d2 100644 --- a/packages/button/src/LionButton.js +++ b/packages/button/src/LionButton.js @@ -11,6 +11,10 @@ import '@lion/core/differentKeyEventNamesShimIE'; const isKeyboardClickEvent = (/** @type {KeyboardEvent} */ e) => e.key === ' ' || e.key === 'Enter'; const isSpaceKeyboardClickEvent = (/** @type {KeyboardEvent} */ e) => e.key === ' '; +/** + * @typedef {import('@lion/core').TemplateResult} TemplateResult + */ + export class LionButton extends DisabledWithTabIndexMixin(SlotMixin(LitElement)) { static get properties() { return { @@ -38,11 +42,21 @@ export class LionButton extends DisabledWithTabIndexMixin(SlotMixin(LitElement)) `; } + /** + * + * @returns {TemplateResult} button template + * @protected + */ // eslint-disable-next-line class-methods-use-this _beforeTemplate() { return html``; } + /** + * + * @returns {TemplateResult} button template + * @protected + */ // eslint-disable-next-line class-methods-use-this _afterTemplate() { return html``; @@ -143,7 +157,10 @@ export class LionButton extends DisabledWithTabIndexMixin(SlotMixin(LitElement)) ]; } - /** @type {HTMLButtonElement} */ + /** + * @type {HTMLButtonElement} + * @protected + */ get _nativeButtonNode() { return /** @type {HTMLButtonElement} */ (Array.from(this.children).find( child => child.slot === '_button', @@ -224,6 +241,7 @@ export class LionButton extends DisabledWithTabIndexMixin(SlotMixin(LitElement)) * of the form, and firing click on this button. This will fire the form submit * without side effects caused by the click bubbling back up to lion-button. * @param {Event} ev + * @private */ async __clickDelegationHandler(ev) { // Wait for updateComplete if form is not yet available @@ -249,18 +267,27 @@ export class LionButton extends DisabledWithTabIndexMixin(SlotMixin(LitElement)) } } + /** + * @private + */ __setupDelegationInConstructor() { // do not move to connectedCallback, otherwise IE11 breaks. // more info: https://github.com/ing-bank/lion/issues/179#issuecomment-511763835 this.addEventListener('click', this.__clickDelegationHandler, true); } + /** + * @private + */ __setupEvents() { this.addEventListener('mousedown', this.__mousedownHandler); this.addEventListener('keydown', this.__keydownHandler); this.addEventListener('keyup', this.__keyupHandler); } + /** + * @private + */ __teardownEvents() { this.removeEventListener('mousedown', this.__mousedownHandler); this.removeEventListener('keydown', this.__keydownHandler); @@ -268,6 +295,9 @@ export class LionButton extends DisabledWithTabIndexMixin(SlotMixin(LitElement)) this.removeEventListener('click', this.__clickDelegationHandler); } + /** + * @private + */ __mousedownHandler() { this.active = true; const mouseupHandler = () => { @@ -281,6 +311,7 @@ export class LionButton extends DisabledWithTabIndexMixin(SlotMixin(LitElement)) /** * @param {KeyboardEvent} e + * @private */ __keydownHandler(e) { if (this.active || !isKeyboardClickEvent(e)) { @@ -309,6 +340,7 @@ export class LionButton extends DisabledWithTabIndexMixin(SlotMixin(LitElement)) /** * @param {KeyboardEvent} e + * @private */ __keyupHandler(e) { if (isKeyboardClickEvent(e)) { @@ -324,6 +356,7 @@ export class LionButton extends DisabledWithTabIndexMixin(SlotMixin(LitElement)) /** * Prevents that someone who listens outside or on form catches the click event * @param {Event} e + * @private */ __preventEventLeakage(e) { if (e.target === this.__submitAndResetHelperButton) { @@ -331,6 +364,9 @@ export class LionButton extends DisabledWithTabIndexMixin(SlotMixin(LitElement)) } } + /** + * @private + */ __setupSubmitAndResetHelperOnConnected() { this._form = this._nativeButtonNode.form; @@ -339,6 +375,9 @@ export class LionButton extends DisabledWithTabIndexMixin(SlotMixin(LitElement)) } } + /** + * @private + */ __teardownSubmitAndResetHelperOnDisconnected() { if (this._form) { this._form.removeEventListener('click', this.__preventEventLeakage); diff --git a/packages/button/test/lion-button.test.js b/packages/button/test/lion-button.test.js index 68563571f..1ae7ab13d 100644 --- a/packages/button/test/lion-button.test.js +++ b/packages/button/test/lion-button.test.js @@ -9,6 +9,16 @@ import '@lion/button/define'; * @typedef {import('@lion/button/src/LionButton').LionButton} LionButton */ +/** + * @param {LionButton} el + */ +function getProtectedMembers(el) { + return { + // @ts-ignore + nativeButtonNode: el._nativeButtonNode, + }; +} + describe('lion-button', () => { it('behaves like native `button` in terms of a11y', async () => { const el = /** @type {LionButton} */ (await fixture(`foo`)); @@ -18,26 +28,32 @@ describe('lion-button', () => { it('has .type="submit" and type="submit" by default', async () => { const el = /** @type {LionButton} */ (await fixture(`foo`)); + const { nativeButtonNode } = getProtectedMembers(el); + expect(el.type).to.equal('submit'); expect(el.getAttribute('type')).to.be.equal('submit'); - expect(el._nativeButtonNode.type).to.equal('submit'); - expect(el._nativeButtonNode.getAttribute('type')).to.be.equal('submit'); + expect(nativeButtonNode.type).to.equal('submit'); + expect(nativeButtonNode.getAttribute('type')).to.be.equal('submit'); }); it('sync type down to the native button', async () => { const el = /** @type {LionButton} */ (await fixture( `foo`, )); + const { nativeButtonNode } = getProtectedMembers(el); + expect(el.type).to.equal('button'); expect(el.getAttribute('type')).to.be.equal('button'); - expect(el._nativeButtonNode.type).to.equal('button'); - expect(el._nativeButtonNode.getAttribute('type')).to.be.equal('button'); + expect(nativeButtonNode.type).to.equal('button'); + expect(nativeButtonNode.getAttribute('type')).to.be.equal('button'); }); it('hides the native button in the UI', async () => { const el = /** @type {LionButton} */ (await fixture(`foo`)); - expect(el._nativeButtonNode.getAttribute('tabindex')).to.equal('-1'); - expect(window.getComputedStyle(el._nativeButtonNode).clip).to.equal('rect(0px, 0px, 0px, 0px)'); + const { nativeButtonNode } = getProtectedMembers(el); + + expect(nativeButtonNode.getAttribute('tabindex')).to.equal('-1'); + expect(window.getComputedStyle(nativeButtonNode).clip).to.equal('rect(0px, 0px, 0px, 0px)'); }); it('is hidden when attribute hidden is true', async () => { @@ -223,8 +239,9 @@ describe('lion-button', () => { it('has a native button node with aria-hidden set to true', async () => { const el = /** @type {LionButton} */ (await fixture('')); + const { nativeButtonNode } = getProtectedMembers(el); - expect(el._nativeButtonNode.getAttribute('aria-hidden')).to.equal('true'); + expect(nativeButtonNode.getAttribute('aria-hidden')).to.equal('true'); }); it('is accessible', async () => { diff --git a/packages/calendar/src/LionCalendar.js b/packages/calendar/src/LionCalendar.js index 2ca302d55..69d4a3127 100644 --- a/packages/calendar/src/LionCalendar.js +++ b/packages/calendar/src/LionCalendar.js @@ -142,7 +142,9 @@ export class LionCalendar extends LocalizeMixin(LitElement) { constructor() { super(); - /** @type {{months: Month[]}} */ + /** @type {{months: Month[]}} + * @private + */ this.__data = { months: [] }; this.minDate = new Date(0); // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date @@ -156,17 +158,27 @@ export class LionCalendar extends LocalizeMixin(LitElement) { this.firstDayOfWeek = 0; this.weekdayHeaderNotation = 'short'; + /** @private */ this.__today = normalizeDateTime(new Date()); /** @type {Date} */ this.centralDate = this.__today; - /** @type {Date | null} */ + /** + * @type {Date | null} + * @private + */ this.__focusedDate = null; + /** @private */ this.__connectedCallbackDone = false; + /** @private */ this.__eventsAdded = false; this.locale = ''; + /** @private */ this.__boundKeyboardNavigationEvent = this.__keyboardNavigationEvent.bind(this); + /** @private */ this.__boundClickDateDelegation = this.__clickDateDelegation.bind(this); + /** @private */ this.__boundFocusDateDelegation = this.__focusDateDelegation.bind(this); + /** @private */ this.__boundBlurDateDelegation = this.__focusDateDelegation.bind(this); } @@ -312,6 +324,9 @@ export class LionCalendar extends LocalizeMixin(LitElement) { } } + /** + * @private + */ __calculateInitialCentralDate() { if (this.centralDate === this.__today && this.selectedDate) { // initialised with selectedDate only if user didn't provide another one @@ -324,6 +339,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) { /** * @param {string} month * @param {number} year + * @private */ __renderMonthNavigation(month, year) { const nextMonth = @@ -348,6 +364,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) { /** * @param {string} month * @param {number} year + * @private */ __renderYearNavigation(month, year) { const nextYear = year + 1; @@ -362,6 +379,9 @@ export class LionCalendar extends LocalizeMixin(LitElement) { `; } + /** + * @private + */ __renderNavigation() { const month = getMonthNames({ locale: this.__getLocale() })[this.centralDate.getMonth()]; const year = this.centralDate.getFullYear(); @@ -372,6 +392,9 @@ export class LionCalendar extends LocalizeMixin(LitElement) { `; } + /** + * @private + */ __renderData() { return dataTemplate(this.__data, { monthsLabels: getMonthNames({ locale: this.__getLocale() }), @@ -393,6 +416,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) { * @param {string} type * @param {string} previousMonth * @param {number} previousYear + * @private */ __getPreviousDisabled(type, previousMonth, previousYear) { let disabled; @@ -417,6 +441,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) { * @param {string} type * @param {string} nextMonth * @param {number} nextYear + * @private */ __getNextDisabled(type, nextMonth, nextYear) { let disabled; @@ -441,6 +466,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) { * @param {string} type * @param {string} previousMonth * @param {number} previousYear + * @private */ __renderPreviousButton(type, previousMonth, previousYear) { const { disabled, month } = this.__getPreviousDisabled(type, previousMonth, previousYear); @@ -470,6 +496,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) { * @param {string} type * @param {string} nextMonth * @param {number} nextYear + * @private */ __renderNextButton(type, nextMonth, nextYear) { const { disabled, month } = this.__getNextDisabled(type, nextMonth, nextYear); @@ -501,6 +528,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) { * @param {string} type * @param {string} month * @param {number} year + * @private */ __getNavigationLabel(mode, type, month, year) { return `${this.msgLit(`lion-calendar:${mode}${type}`)}, ${month} ${year}`; @@ -510,6 +538,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) { * * @param {Day} _day * @param {*} param1 + * @private */ __coreDayPreprocessor(_day, { currentMonth = false } = {}) { const day = createDay(new Date(_day.date), _day); @@ -543,6 +572,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) { /** * @param {Day} [options] + * @private */ __createData(options) { const data = createMultipleMonth(this.centralDate, { @@ -564,6 +594,9 @@ export class LionCalendar extends LocalizeMixin(LitElement) { return data; } + /** + * @private + */ __disableDatesChanged() { if (this.__connectedCallbackDone) { this.__ensureValidCentralDate(); @@ -572,6 +605,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) { /** * @param {Date} selectedDate + * @private */ __dateSelectedByUser(selectedDate) { this.selectedDate = selectedDate; @@ -585,18 +619,27 @@ export class LionCalendar extends LocalizeMixin(LitElement) { ); } + /** + * @private + */ __centralDateChanged() { if (this.__connectedCallbackDone) { this.__ensureValidCentralDate(); } } + /** + * @private + */ __focusedDateChanged() { if (this.__focusedDate) { this.centralDate = this.__focusedDate; } } + /** + * @private + */ __ensureValidCentralDate() { if (!this.__isEnabledDate(this.centralDate)) { this.centralDate = this.__findBestEnabledDateFor(this.centralDate); @@ -605,6 +648,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) { /** * @param {Date} date + * @private */ __isEnabledDate(date) { const processedDay = this.__coreDayPreprocessor({ date }); @@ -615,6 +659,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) { * @param {Date} date * @param {Object} opts * @param {String} [opts.mode] Find best date in `future/past/both` + * @private */ __findBestEnabledDateFor(date, { mode = 'both' } = {}) { const futureDate = @@ -655,6 +700,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) { /** * @param {Event} ev + * @private */ __clickDateDelegation(ev) { const isDayButton = /** @param {HTMLElement} el */ el => @@ -666,6 +712,9 @@ export class LionCalendar extends LocalizeMixin(LitElement) { } } + /** + * @private + */ __focusDateDelegation() { const isDayButton = /** @param {HTMLElement} el */ el => el.classList.contains('calendar__day-button'); @@ -679,6 +728,9 @@ export class LionCalendar extends LocalizeMixin(LitElement) { } } + /** + * @private + */ __blurDateDelegation() { const isDayButton = /** @param {HTMLElement} el */ el => el.classList.contains('calendar__day-button'); @@ -695,6 +747,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) { /** * @param {KeyboardEvent} ev + * @private */ __keyboardNavigationEvent(ev) { const preventedKeys = ['ArrowUp', 'ArrowDown', 'PageDown', 'PageUp']; @@ -744,6 +797,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) { * @param {string} opts.dateType * @param {string} opts.type * @param {string} opts.mode + * @private */ __modifyDate(modify, { dateType, type, mode }) { let tmpDate = new Date(this.centralDate); @@ -765,6 +819,9 @@ export class LionCalendar extends LocalizeMixin(LitElement) { this[dateType] = tmpDate; } + /** + * @private + */ __getLocale() { return this.locale || localize.locale; } diff --git a/packages/checkbox-group/src/LionCheckboxIndeterminate.js b/packages/checkbox-group/src/LionCheckboxIndeterminate.js index 11688c4fe..66707f3da 100644 --- a/packages/checkbox-group/src/LionCheckboxIndeterminate.js +++ b/packages/checkbox-group/src/LionCheckboxIndeterminate.js @@ -34,10 +34,16 @@ export class LionCheckboxIndeterminate extends LionCheckbox { }; } + /** + * @protected + */ get _checkboxGroupNode() { return /** @type LionCheckboxGroup */ (this._parentFormGroup); } + /** + * @protected + */ get _subCheckboxes() { let checkboxes = []; if ( @@ -52,6 +58,9 @@ export class LionCheckboxIndeterminate extends LionCheckbox { return /** @type LionCheckbox[] */ (checkboxes); } + /** + * @protected + */ _setOwnCheckedState() { const subCheckboxes = this._subCheckboxes; if (!subCheckboxes.length) { @@ -82,6 +91,7 @@ export class LionCheckboxIndeterminate extends LionCheckbox { /** * @param {Event} ev + * @private */ __onModelValueChanged(ev) { if (this.disabled) { @@ -98,6 +108,9 @@ export class LionCheckboxIndeterminate extends LionCheckbox { this._setOwnCheckedState(); } + /** + * @protected + */ // eslint-disable-next-line class-methods-use-this _afterTemplate() { return html` @@ -107,6 +120,9 @@ export class LionCheckboxIndeterminate extends LionCheckbox { `; } + /** + * @protected + */ _onRequestToAddFormElement() { this._setOwnCheckedState(); } diff --git a/packages/checkbox-group/test/lion-checkbox-indeterminate.test.js b/packages/checkbox-group/test/lion-checkbox-indeterminate.test.js index 9023f9ebe..6415bb3c1 100644 --- a/packages/checkbox-group/test/lion-checkbox-indeterminate.test.js +++ b/packages/checkbox-group/test/lion-checkbox-indeterminate.test.js @@ -6,6 +6,16 @@ import '@lion/checkbox-group/define'; * @typedef {import('../src/LionCheckboxGroup').LionCheckboxGroup} LionCheckboxGroup */ +/** + * @param {LionCheckboxIndeterminate} el + */ +function getProtectedMembers(el) { + return { + // @ts-ignore + subCheckboxes: el._subCheckboxes, + }; +} + describe('', () => { it('should have type = checkbox', async () => { // Arrange @@ -93,8 +103,10 @@ describe('', () => { 'lion-checkbox-indeterminate', )); + const { subCheckboxes } = getProtectedMembers(elIndeterminate); + // Act - elIndeterminate._subCheckboxes[0].checked = true; + subCheckboxes[0].checked = true; await el.updateComplete; // Assert @@ -115,11 +127,12 @@ describe('', () => { const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector( 'lion-checkbox-indeterminate', )); + const { subCheckboxes } = getProtectedMembers(elIndeterminate); // Act - elIndeterminate._subCheckboxes[0].checked = true; - elIndeterminate._subCheckboxes[1].checked = true; - elIndeterminate._subCheckboxes[2].checked = true; + subCheckboxes[0].checked = true; + subCheckboxes[1].checked = true; + subCheckboxes[2].checked = true; await el.updateComplete; // Assert @@ -145,12 +158,13 @@ describe('', () => { // Act elIndeterminate._inputNode.click(); await elIndeterminate.updateComplete; + const { subCheckboxes } = getProtectedMembers(elIndeterminate); // Assert - expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.false; - expect(elIndeterminate?._subCheckboxes[0].hasAttribute('checked')).to.be.true; - expect(elIndeterminate?._subCheckboxes[1].hasAttribute('checked')).to.be.true; - expect(elIndeterminate?._subCheckboxes[2].hasAttribute('checked')).to.be.true; + expect(elIndeterminate.hasAttribute('indeterminate')).to.be.false; + expect(subCheckboxes[0].hasAttribute('checked')).to.be.true; + expect(subCheckboxes[1].hasAttribute('checked')).to.be.true; + expect(subCheckboxes[2].hasAttribute('checked')).to.be.true; }); it('should sync all children when parent is checked (from unchecked to checked)', async () => { @@ -171,12 +185,13 @@ describe('', () => { // Act elIndeterminate._inputNode.click(); await elIndeterminate.updateComplete; + const { subCheckboxes } = getProtectedMembers(elIndeterminate); // Assert - expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.false; - expect(elIndeterminate?._subCheckboxes[0].hasAttribute('checked')).to.be.true; - expect(elIndeterminate?._subCheckboxes[1].hasAttribute('checked')).to.be.true; - expect(elIndeterminate?._subCheckboxes[2].hasAttribute('checked')).to.be.true; + expect(elIndeterminate.hasAttribute('indeterminate')).to.be.false; + expect(subCheckboxes[0].hasAttribute('checked')).to.be.true; + expect(subCheckboxes[1].hasAttribute('checked')).to.be.true; + expect(subCheckboxes[2].hasAttribute('checked')).to.be.true; }); it('should sync all children when parent is checked (from checked to unchecked)', async () => { @@ -197,12 +212,13 @@ describe('', () => { // Act elIndeterminate._inputNode.click(); await elIndeterminate.updateComplete; + const elProts = getProtectedMembers(elIndeterminate); // Assert expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.false; - expect(elIndeterminate?._subCheckboxes[0].hasAttribute('checked')).to.be.false; - expect(elIndeterminate?._subCheckboxes[1].hasAttribute('checked')).to.be.false; - expect(elIndeterminate?._subCheckboxes[2].hasAttribute('checked')).to.be.false; + expect(elProts.subCheckboxes[0].hasAttribute('checked')).to.be.false; + expect(elProts.subCheckboxes[1].hasAttribute('checked')).to.be.false; + expect(elProts.subCheckboxes[2].hasAttribute('checked')).to.be.false; }); it('should work as expected with siblings checkbox-indeterminate', async () => { @@ -251,13 +267,18 @@ describe('', () => { await elFirstIndeterminate.updateComplete; await elSecondIndeterminate.updateComplete; + const elFirstSubCheckboxes = getProtectedMembers(elFirstIndeterminate); + const elSecondSubCheckboxes = getProtectedMembers(elSecondIndeterminate); + // Assert - the second sibling should not be affected - expect(elFirstIndeterminate?.hasAttribute('indeterminate')).to.be.false; - expect(elFirstIndeterminate?._subCheckboxes[0].hasAttribute('checked')).to.be.true; - expect(elFirstIndeterminate?._subCheckboxes[1].hasAttribute('checked')).to.be.true; - expect(elFirstIndeterminate?._subCheckboxes[2].hasAttribute('checked')).to.be.true; - expect(elSecondIndeterminate?._subCheckboxes[0].hasAttribute('checked')).to.be.false; - expect(elSecondIndeterminate?._subCheckboxes[1].hasAttribute('checked')).to.be.false; + + expect(elFirstIndeterminate.hasAttribute('indeterminate')).to.be.false; + expect(elFirstSubCheckboxes.subCheckboxes[0].hasAttribute('checked')).to.be.true; + expect(elFirstSubCheckboxes.subCheckboxes[1].hasAttribute('checked')).to.be.true; + expect(elFirstSubCheckboxes.subCheckboxes[2].hasAttribute('checked')).to.be.true; + + expect(elSecondSubCheckboxes.subCheckboxes[0].hasAttribute('checked')).to.be.false; + expect(elSecondSubCheckboxes.subCheckboxes[1].hasAttribute('checked')).to.be.false; }); it('should work as expected with nested indeterminate checkboxes', async () => { @@ -301,9 +322,13 @@ describe('', () => { const elParentIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector( '#parent-checkbox-indeterminate', )); + const elNestedSubCheckboxes = getProtectedMembers(elNestedIndeterminate); + const elParentSubCheckboxes = getProtectedMembers(elParentIndeterminate); // Act - check a nested checkbox - elNestedIndeterminate?._subCheckboxes[0]._inputNode.click(); + if (elNestedIndeterminate) { + elNestedSubCheckboxes.subCheckboxes[0]._inputNode.click(); + } await el.updateComplete; // Assert @@ -311,8 +336,8 @@ describe('', () => { expect(elParentIndeterminate?.hasAttribute('indeterminate')).to.be.true; // Act - check all nested checkbox - elNestedIndeterminate?._subCheckboxes[1]._inputNode.click(); - elNestedIndeterminate?._subCheckboxes[2]._inputNode.click(); + if (elNestedIndeterminate) elNestedSubCheckboxes.subCheckboxes[1]._inputNode.click(); + if (elNestedIndeterminate) elNestedSubCheckboxes.subCheckboxes[2]._inputNode.click(); await el.updateComplete; // Assert @@ -322,8 +347,12 @@ describe('', () => { expect(elParentIndeterminate?.hasAttribute('indeterminate')).to.be.true; // Act - finally check all remaining checkbox - elParentIndeterminate?._subCheckboxes[0]._inputNode.click(); - elParentIndeterminate?._subCheckboxes[1]._inputNode.click(); + if (elParentIndeterminate) { + elParentSubCheckboxes.subCheckboxes[0]._inputNode.click(); + } + if (elParentIndeterminate) { + elParentSubCheckboxes.subCheckboxes[1]._inputNode.click(); + } await el.updateComplete; // Assert @@ -354,11 +383,12 @@ describe('', () => { const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector( 'lion-checkbox-indeterminate', )); + const { subCheckboxes } = getProtectedMembers(elIndeterminate); // Act - elIndeterminate._subCheckboxes[0].checked = true; - elIndeterminate._subCheckboxes[1].checked = true; - elIndeterminate._subCheckboxes[2].checked = true; + subCheckboxes[0].checked = true; + subCheckboxes[1].checked = true; + subCheckboxes[2].checked = true; await el.updateComplete; // Assert diff --git a/packages/collapsible/src/LionCollapsible.js b/packages/collapsible/src/LionCollapsible.js index 55985a765..da520e244 100644 --- a/packages/collapsible/src/LionCollapsible.js +++ b/packages/collapsible/src/LionCollapsible.js @@ -127,18 +127,27 @@ export class LionCollapsible extends LitElement { // eslint-disable-next-line class-methods-use-this, no-empty-function, no-unused-vars async _hideAnimation(opts) {} + /** + * @protected + */ get _invokerNode() { return /** @type {HTMLElement[]} */ (Array.from(this.children)).find( child => child.slot === 'invoker', ); } + /** + * @protected + */ get _contentNode() { return /** @type {HTMLElement[]} */ (Array.from(this.children)).find( child => child.slot === 'content', ); } + /** + * @protected + */ get _contentHeight() { const size = this._contentNode?.getBoundingClientRect().height || 0; return `${size}px`; diff --git a/packages/collapsible/test/lion-collapsible.test.js b/packages/collapsible/test/lion-collapsible.test.js index 8a8f23d70..f7ec57a69 100644 --- a/packages/collapsible/test/lion-collapsible.test.js +++ b/packages/collapsible/test/lion-collapsible.test.js @@ -33,6 +33,16 @@ const collapsibleWithEvents = html` `; +/** + * @param {LionCollapsible} el + */ +function getProtectedMembers(el) { + return { + // @ts-ignore + contentHeight: el._contentHeight, + }; +} + describe('', () => { describe('Collapsible', () => { it('sets opened to false by default', async () => { @@ -49,10 +59,12 @@ describe('', () => { it('should return content node height before and after collapsing', async () => { const collapsible = await fixture(defaultCollapsible); - expect(collapsible._contentHeight).to.equal('0px'); + const collHeight1 = getProtectedMembers(collapsible); + expect(collHeight1.contentHeight).to.equal('0px'); collapsible.show(); await collapsible.requestUpdate(); - expect(collapsible._contentHeight).to.equal('32px'); + const collHeight2 = getProtectedMembers(collapsible); + expect(collHeight2.contentHeight).to.equal('32px'); }); }); diff --git a/packages/combobox/src/LionCombobox.js b/packages/combobox/src/LionCombobox.js index 0bd9340c4..9fe2cb97f 100644 --- a/packages/combobox/src/LionCombobox.js +++ b/packages/combobox/src/LionCombobox.js @@ -71,6 +71,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @enhance FormControlMixin - add slot[name=selection-display] + * @protected */ // eslint-disable-next-line class-methods-use-this _inputGroupInputTemplate() { @@ -83,6 +84,9 @@ export class LionCombobox extends OverlayMixin(LionListbox) { `; } + /** + * @protected + */ // eslint-disable-next-line class-methods-use-this _overlayListboxTemplate() { // TODO: Localize the aria-label @@ -96,6 +100,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @enhance FormControlMixin - add overlay + * @protected */ _groupTwoTemplate() { return html` ${super._groupTwoTemplate()} ${this._overlayListboxTemplate()}`; @@ -157,6 +162,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * Wrapper with combobox role for the text input that the end user controls the listbox with. * @type {HTMLElement} + * @protected */ get _comboboxNode() { return /** @type {HTMLElement} */ (this.querySelector('[slot="input"]')); @@ -164,6 +170,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @type {SelectionDisplay | null} + * @protected */ get _selectionDisplayNode() { return this.querySelector('[slot="selection-display"]'); @@ -173,6 +180,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { * @configure FormControlMixin * Will tell FormControlMixin that a11y wrt labels / descriptions / feedback * should be applied here. + * @protected */ get _inputNode() { if (this._ariaVersion === '1.1') { @@ -183,6 +191,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @configure OverlayMixin + * @protected */ get _overlayContentNode() { return this._listboxNode; @@ -190,6 +199,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @configure OverlayMixin + * @protected */ get _overlayReferenceNode() { return /** @type {ShadowRoot} */ (this.shadowRoot).querySelector('.input-group__container'); @@ -197,6 +207,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @configure OverlayMixin + * @protected */ get _overlayInvokerNode() { return this._inputNode; @@ -204,6 +215,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @configure ListboxMixin + * @protected */ get _listboxNode() { return /** @type {LionOptions} */ ((this._overlayCtrl && this._overlayCtrl.contentNode) || @@ -212,6 +224,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @configure ListboxMixin + * @protected */ get _activeDescendantOwnerNode() { return this._inputNode; @@ -253,22 +266,36 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * For optimal support, we allow aria v1.1 on newer browsers * @type {'1.1'|'1.0'} + * @protected */ this._ariaVersion = browserDetection.isChromium ? '1.1' : '1.0'; /** * @configure ListboxMixin + * @protected */ this._listboxReceivesNoFocus = true; + /** + * @private + */ this.__prevCboxValueNonSelected = ''; + /** + * @private + */ this.__prevCboxValue = ''; - /** @type {EventListener} */ + /** @type {EventListener} + * @private + */ this.__requestShowOverlay = this.__requestShowOverlay.bind(this); - /** @type {EventListener} */ + /** @type {EventListener} + * @protected + */ this._textboxOnInput = this._textboxOnInput.bind(this); - /** @type {EventListener} */ + /** @type {EventListener} + * @protected + */ this._textboxOnKeydown = this._textboxOnKeydown.bind(this); } @@ -387,6 +414,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { * } * * @param {{ currentValue: string, lastKey:string }} options + * @protected */ // eslint-disable-next-line class-methods-use-this _showOverlayCondition({ lastKey }) { @@ -403,6 +431,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @param {Event} ev + * @protected */ // eslint-disable-next-line no-unused-vars _textboxOnInput(ev) { @@ -412,6 +441,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @param {KeyboardEvent} ev + * @protected */ _textboxOnKeydown(ev) { if (ev.key === 'Tab') { @@ -421,6 +451,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @param {MouseEvent} ev + * @protected */ _listboxOnClick(ev) { super._listboxOnClick(ev); @@ -433,6 +464,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @param {string} v + * @protected */ _setTextboxValue(v) { // Make sure that we don't loose inputNode.selectionStart and inputNode.selectionEnd @@ -441,6 +473,9 @@ export class LionCombobox extends OverlayMixin(LionListbox) { } } + /** + * @private + */ __onOverlayClose() { if (!this.multipleChoice) { if ( @@ -471,6 +506,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { * model-value-changed event that gets received, and we should repropagate it. * * @param {EventTarget & import('../types/choice-group/ChoiceInputMixinTypes').ChoiceInputHost} target + * @protected */ _repropagationCondition(target) { return super._repropagationCondition(target) || this.formElements.every(el => !el.checked); @@ -481,6 +517,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { * @overridable * @param {LionOption & {__originalInnerHTML?:string}} option * @param {string} matchingString + * @protected */ // eslint-disable-next-line class-methods-use-this _onFilterMatch(option, matchingString) { @@ -498,6 +535,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { * @param {LionOption & {__originalInnerHTML?:string}} option * @param {string} [curValue] * @param {string} [prevValue] + * @protected */ // eslint-disable-next-line no-unused-vars, class-methods-use-this _onFilterUnmatch(option, curValue, prevValue) { @@ -512,6 +550,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * Computes whether a user intends to autofill (inline autocomplete textbox) * @param {{ prevValue:string, curValue:string }} config + * @private */ // eslint-disable-next-line class-methods-use-this __computeUserIntendsAutoFill({ prevValue, curValue }) { @@ -535,6 +574,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { * - complete: completes the textbox value inline (the 'missing characters' will be added as * selected text) * + * @protected */ _handleAutocompletion() { // TODO: this is captured by 'noFilter' @@ -660,6 +700,9 @@ export class LionCombobox extends OverlayMixin(LionListbox) { } } + /** + * @private + */ __textboxInlineComplete(option = this.formElements[this.activeIndex]) { const prevLen = this._inputNode.value.length; this._inputNode.value = option.choiceValue; @@ -672,6 +715,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { * option from the list (by default when autocomplete is 'none' or 'list'). * For autocomplete 'both' or 'inline', it will automatically select on a match. * @overridable + * @protected */ _autoSelectCondition() { return this.autocomplete === 'both' || this.autocomplete === 'inline'; @@ -679,6 +723,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @enhance ListboxMixin + * @protected */ _setupListboxNode() { super._setupListboxNode(); @@ -688,6 +733,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @configure OverlayMixin + * @protected */ // eslint-disable-next-line class-methods-use-this _defineOverlayConfig() { @@ -699,6 +745,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @enhance OverlayMixin + * @protected */ _setupOverlayCtrl() { super._setupOverlayCtrl(); @@ -708,6 +755,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @enhance OverlayMixin + * @protected */ _setupOpenCloseListeners() { super._setupOpenCloseListeners(); @@ -716,6 +764,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @enhance OverlayMixin + * @protected */ _teardownOpenCloseListeners() { super._teardownOpenCloseListeners(); @@ -725,6 +774,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @enhance ListboxMixin * @param {KeyboardEvent} ev + * @protected */ _listboxOnKeyDown(ev) { super._listboxOnKeyDown(ev); @@ -750,6 +800,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { * @overridable * @param {string|string[]} modelValue * @param {string|string[]} oldModelValue + * @protected */ // eslint-disable-next-line no-unused-vars _syncToTextboxCondition(modelValue, oldModelValue, { phase } = {}) { @@ -763,6 +814,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { * Allows to control what happens when checkedIndexes change * @param {string[]} modelValue * @param {string[]} oldModelValue + * @protected */ _syncToTextboxMultiple(modelValue, oldModelValue = []) { const diff = modelValue.filter(x => !oldModelValue.includes(x)); @@ -771,6 +823,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @override FormControlMixin - add form-control to [slot=input] instead of _inputNode + * @protected */ _enhanceLightDomClasses() { if (this.querySelector('[slot=input]')) { @@ -778,10 +831,16 @@ export class LionCombobox extends OverlayMixin(LionListbox) { } } + /** + * @private + */ __initFilterListbox() { this._handleAutocompletion(); } + /** + * @private + */ __setComboboxDisabledAndReadOnly() { if (this._comboboxNode) { this._comboboxNode.setAttribute('disabled', `${this.disabled}`); @@ -789,6 +848,9 @@ export class LionCombobox extends OverlayMixin(LionListbox) { } } + /** + * @private + */ __setupCombobox() { // With regard to accessibility: aria-expanded and -labelledby will // be handled by OverlayMixin and FormControlMixin respectively. @@ -811,6 +873,9 @@ export class LionCombobox extends OverlayMixin(LionListbox) { this._inputNode.addEventListener('keydown', this._textboxOnKeydown); } + /** + * @private + */ __teardownCombobox() { this._inputNode.removeEventListener('keydown', this._listboxOnKeyDown); this._inputNode.removeEventListener('input', this._textboxOnInput); @@ -819,6 +884,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { /** * @param {KeyboardEvent} [ev] + * @private */ __requestShowOverlay(ev) { this.opened = this._showOverlayCondition({ diff --git a/packages/combobox/test/lion-combobox.test.js b/packages/combobox/test/lion-combobox.test.js index 4c3131c41..95e85b776 100644 --- a/packages/combobox/test/lion-combobox.test.js +++ b/packages/combobox/test/lion-combobox.test.js @@ -11,17 +11,42 @@ import { LionCombobox } from '../src/LionCombobox.js'; * @typedef {import('../types/SelectionDisplay').SelectionDisplay} SelectionDisplay */ +/** + * @param {LionCombobox} el + */ + +function getProtectedMembers(el) { + // @ts-ignore + const { + _comboboxNode: comboboxNode, + _inputNode: inputNode, + _listboxNode: listboxNode, + _selectionDisplayNode: selectionDisplayNode, + _activeDescendantOwnerNode: activeDescendantOwnerNode, + _ariaVersion: ariaVersion, + } = el; + return { + comboboxNode, + inputNode, + listboxNode, + selectionDisplayNode, + activeDescendantOwnerNode, + ariaVersion, + }; +} + /** * @param {LionCombobox} el * @param {string} value */ function mimicUserTyping(el, value) { - el._inputNode.dispatchEvent(new Event('focusin', { bubbles: true })); + const { inputNode } = getProtectedMembers(el); + inputNode.dispatchEvent(new Event('focusin', { bubbles: true })); // eslint-disable-next-line no-param-reassign - el._inputNode.value = value; - el._inputNode.dispatchEvent(new Event('input', { bubbles: true, composed: true })); - el._inputNode.dispatchEvent(new KeyboardEvent('keyup', { key: value })); - el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: value })); + inputNode.value = value; + inputNode.dispatchEvent(new Event('input', { bubbles: true, composed: true })); + inputNode.dispatchEvent(new KeyboardEvent('keyup', { key: value })); + inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: value })); } /** @@ -38,35 +63,49 @@ function mimicKeyPress(el, key) { * @param {string[]} values */ async function mimicUserTypingAdvanced(el, values) { - const inputNode = /** @type {HTMLInputElement & {selectionStart:number, selectionEnd:number}} */ (el._inputNode); - inputNode.dispatchEvent(new Event('focusin', { bubbles: true })); + const { inputNode } = getProtectedMembers(el); + const inputNodeLoc = /** @type {HTMLInputElement & {selectionStart:number, selectionEnd:number}} */ (inputNode); + inputNodeLoc.dispatchEvent(new Event('focusin', { bubbles: true })); for (const key of values) { // eslint-disable-next-line no-await-in-loop, no-loop-func await new Promise(resolve => { - const hasSelection = inputNode.selectionStart !== inputNode.selectionEnd; + const hasSelection = inputNodeLoc.selectionStart !== inputNodeLoc.selectionEnd; if (key === 'Backspace') { if (hasSelection) { - inputNode.value = - inputNode.value.slice(0, inputNode.selectionStart) + - inputNode.value.slice(inputNode.selectionEnd, inputNode.value.length); + inputNodeLoc.value = + inputNodeLoc.value.slice( + 0, + inputNodeLoc.selectionStart ? inputNodeLoc.selectionStart : undefined, + ) + + inputNodeLoc.value.slice( + inputNodeLoc.selectionEnd ? inputNodeLoc.selectionEnd : undefined, + inputNodeLoc.value.length, + ); } else { - inputNode.value = inputNode.value.slice(0, -1); + inputNodeLoc.value = inputNodeLoc.value.slice(0, -1); } } else if (hasSelection) { - inputNode.value = - inputNode.value.slice(0, inputNode.selectionStart) + + inputNodeLoc.value = + inputNodeLoc.value.slice( + 0, + inputNodeLoc.selectionStart ? inputNodeLoc.selectionStart : undefined, + ) + key + - inputNode.value.slice(inputNode.selectionEnd, inputNode.value.length); + inputNodeLoc.value.slice( + inputNodeLoc.selectionEnd ? inputNodeLoc.selectionEnd : undefined, + inputNodeLoc.value.length, + ); } else { - inputNode.value += key; + inputNodeLoc.value += key; } - mimicKeyPress(inputNode, key); - el._inputNode.dispatchEvent(new Event('input', { bubbles: true, composed: true })); + mimicKeyPress(inputNodeLoc, key); + inputNodeLoc.dispatchEvent(new Event('input', { bubbles: true, composed: true })); el.updateComplete.then(() => { + // @ts-ignore resolve(); }); }); @@ -187,9 +226,10 @@ describe('lion-combobox', () => { Victoria Plum `)); + const { comboboxNode } = getProtectedMembers(el); expect(el.opened).to.be.false; - el._comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); await el.updateComplete; expect(el.opened).to.be.true; }); @@ -204,9 +244,11 @@ describe('lion-combobox', () => { Item 2 `)); - expect(el._listboxNode).to.exist; - expect(el._listboxNode).to.be.instanceOf(LionOptions); - expect(el.querySelector('[role=listbox]')).to.equal(el._listboxNode); + const { listboxNode } = getProtectedMembers(el); + + expect(listboxNode).to.exist; + expect(listboxNode).to.be.instanceOf(LionOptions); + expect(el.querySelector('[role=listbox]')).to.equal(listboxNode); }); it('has a textbox element', async () => { @@ -216,8 +258,10 @@ describe('lion-combobox', () => { Item 2 `)); - expect(el._comboboxNode).to.exist; - expect(el.querySelector('[role=combobox]')).to.equal(el._comboboxNode); + const { comboboxNode } = getProtectedMembers(el); + + expect(comboboxNode).to.exist; + expect(el.querySelector('[role=combobox]')).to.equal(comboboxNode); }); }); @@ -229,11 +273,13 @@ describe('lion-combobox', () => { Item 2 `)); - expect(el._inputNode.value).to.equal('10'); + const { inputNode } = getProtectedMembers(el); + + expect(inputNode.value).to.equal('10'); el.modelValue = '20'; await el.updateComplete; - expect(el._inputNode.value).to.equal('20'); + expect(inputNode.value).to.equal('20'); }); it('sets modelValue to empty string if no option is selected', async () => { @@ -283,9 +329,10 @@ describe('lion-combobox', () => { Victoria Plum `)); + const { comboboxNode } = getProtectedMembers(el); expect(el.opened).to.equal(false); - el._comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); await el.updateComplete; expect(el.opened).to.equal(false); }); @@ -308,10 +355,12 @@ describe('lion-combobox', () => { `)); const options = el.formElements; + const { inputNode } = getProtectedMembers(el); + expect(el.opened).to.equal(false); // step [1] - el._inputNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + inputNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); await el.updateComplete; expect(el.opened).to.equal(false); @@ -324,7 +373,7 @@ describe('lion-combobox', () => { options[0].click(); await el.updateComplete; expect(el.opened).to.equal(false); - expect(document.activeElement).to.equal(el._inputNode); + expect(document.activeElement).to.equal(inputNode); // step [4] await el.updateComplete; @@ -343,17 +392,19 @@ describe('lion-combobox', () => { `)); + const { comboboxNode, inputNode } = getProtectedMembers(el); + // open - el._comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); mimicUserTyping(el, 'art'); await el.updateComplete; expect(el.opened).to.equal(true); - expect(el._inputNode.value).to.equal('Artichoke'); + expect(inputNode.value).to.equal('Artichoke'); - el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); + inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); expect(el.opened).to.equal(false); - expect(el._inputNode.value).to.equal(''); + expect(inputNode.value).to.equal(''); }); it('hides overlay on [Tab]', async () => { @@ -366,17 +417,19 @@ describe('lion-combobox', () => { `)); + const { comboboxNode, inputNode } = getProtectedMembers(el); + // open - el._comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); mimicUserTyping(el, 'art'); await el.updateComplete; expect(el.opened).to.equal(true); - expect(el._inputNode.value).to.equal('Artichoke'); + expect(inputNode.value).to.equal('Artichoke'); - mimicKeyPress(el._inputNode, 'Tab'); + mimicKeyPress(inputNode, 'Tab'); expect(el.opened).to.equal(false); - expect(el._inputNode.value).to.equal('Artichoke'); + expect(inputNode.value).to.equal('Artichoke'); }); it('clears checkedIndex on empty text', async () => { @@ -389,13 +442,15 @@ describe('lion-combobox', () => { `)); + const { comboboxNode, inputNode } = getProtectedMembers(el); + // open - el._comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); mimicUserTyping(el, 'art'); await el.updateComplete; expect(el.opened).to.equal(true); - expect(el._inputNode.value).to.equal('Artichoke'); + expect(inputNode.value).to.equal('Artichoke'); expect(el.checkedIndex).to.equal(0); mimicUserTyping(el, ''); @@ -425,9 +480,10 @@ describe('lion-combobox', () => { Victoria Plum `)); + const { comboboxNode } = getProtectedMembers(el); expect(el.opened).to.equal(false); - el._comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); await el.updateComplete; expect(el.opened).to.equal(true); }); @@ -517,9 +573,10 @@ describe('lion-combobox', () => { `)); const options = el.formElements; + const { comboboxNode } = getProtectedMembers(el); expect(el.opened).to.equal(false); - el._comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); mimicUserTyping(el, 'art'); await el.updateComplete; @@ -557,9 +614,10 @@ describe('lion-combobox', () => { `)); const options = el.formElements; + const { comboboxNode } = getProtectedMembers(el); expect(el.opened).to.equal(false); - el._comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); mimicUserTyping(el, 'art'); expect(el.opened).to.equal(true); @@ -584,14 +642,15 @@ describe('lion-combobox', () => { Victoria Plum `)); + const { inputNode } = getProtectedMembers(el); expect(el.checkedIndex).to.equal(0); // Simulate backspace deleting the char at the end of the string - mimicKeyPress(el._inputNode, 'Backspace'); - el._inputNode.dispatchEvent(new Event('input')); - const arr = el._inputNode.value.split(''); - arr.splice(el._inputNode.value.length - 1, 1); - el._inputNode.value = arr.join(''); + mimicKeyPress(inputNode, 'Backspace'); + inputNode.dispatchEvent(new Event('input')); + const arr = inputNode.value.split(''); + arr.splice(inputNode.value.length - 1, 1); + inputNode.value = arr.join(''); await el.updateComplete; el.dispatchEvent(new Event('blur')); @@ -614,7 +673,9 @@ describe('lion-combobox', () => { Item 2 `)); - expect(el._comboboxNode.getAttribute('role')).to.equal('combobox'); + const { comboboxNode } = getProtectedMembers(el); + + expect(comboboxNode.getAttribute('role')).to.equal('combobox'); }); it('makes sure listbox node is not focusable', async () => { @@ -624,7 +685,9 @@ describe('lion-combobox', () => { Item 2 `)); - expect(el._listboxNode.hasAttribute('tabindex')).to.be.false; + const { listboxNode } = getProtectedMembers(el); + + expect(listboxNode.hasAttribute('tabindex')).to.be.false; }); }); }); @@ -653,7 +716,9 @@ describe('lion-combobox', () => { Item 1 `)); - expect(el._selectionDisplayNode).to.equal(el.querySelector('[slot=selection-display]')); + const { selectionDisplayNode } = getProtectedMembers(el); + + expect(selectionDisplayNode).to.equal(el.querySelector('[slot=selection-display]')); }); it('sets a reference to combobox element in _selectionDisplayNode', async () => { @@ -781,14 +846,16 @@ describe('lion-combobox', () => { `)); mimicUserTyping(el, 'ch'); await el.updateComplete; - expect(el._inputNode.value).to.equal('Chard'); - expect(el._inputNode.selectionStart).to.equal(2); - expect(el._inputNode.selectionEnd).to.equal(el._inputNode.value.length); + const { inputNode } = getProtectedMembers(el); + + expect(inputNode.value).to.equal('Chard'); + expect(inputNode.selectionStart).to.equal(2); + expect(inputNode.selectionEnd).to.equal(inputNode.value.length); // We don't autocomplete when characters are removed mimicUserTyping(el, 'c'); // The user pressed backspace (number of chars decreased) - expect(el._inputNode.value).to.equal('c'); - expect(el._inputNode.selectionStart).to.equal(el._inputNode.value.length); + expect(inputNode.value).to.equal('c'); + expect(inputNode.selectionStart).to.equal(inputNode.value.length); }); it('filters options when autocomplete is "list"', async () => { @@ -800,10 +867,12 @@ describe('lion-combobox', () => { Victoria Plum `)); + const { inputNode } = getProtectedMembers(el); + mimicUserTyping(el, 'ch'); await el.updateComplete; expect(getFilteredOptionValues(el)).to.eql(['Artichoke', 'Chard', 'Chicory']); - expect(el._inputNode.value).to.equal('ch'); + expect(inputNode.value).to.equal('ch'); }); it('does not filter options when autocomplete is "none"', async () => { @@ -891,25 +960,26 @@ describe('lion-combobox', () => { Victoria Plum `)); + const { inputNode } = getProtectedMembers(el); mimicUserTyping(el, 'ch'); await el.updateComplete; - expect(el._inputNode.value).to.equal('Chard'); - expect(el._inputNode.selectionStart).to.equal('ch'.length); - expect(el._inputNode.selectionEnd).to.equal('Chard'.length); + expect(inputNode.value).to.equal('Chard'); + expect(inputNode.selectionStart).to.equal('ch'.length); + expect(inputNode.selectionEnd).to.equal('Chard'.length); await mimicUserTypingAdvanced(el, ['i', 'c']); await el.updateComplete; - expect(el._inputNode.value).to.equal('Chicory'); - expect(el._inputNode.selectionStart).to.equal('chic'.length); - expect(el._inputNode.selectionEnd).to.equal('Chicory'.length); + expect(inputNode.value).to.equal('Chicory'); + expect(inputNode.selectionStart).to.equal('chic'.length); + expect(inputNode.selectionEnd).to.equal('Chicory'.length); // Diminishing chars, but autocompleting mimicUserTyping(el, 'ch'); await el.updateComplete; - expect(el._inputNode.value).to.equal('ch'); - expect(el._inputNode.selectionStart).to.equal('ch'.length); - expect(el._inputNode.selectionEnd).to.equal('ch'.length); + expect(inputNode.value).to.equal('ch'); + expect(inputNode.selectionStart).to.equal('ch'.length); + expect(inputNode.selectionEnd).to.equal('ch'.length); }); it('synchronizes textbox on overlay close', async () => { @@ -921,7 +991,8 @@ describe('lion-combobox', () => { Victoria Plum `)); - expect(el._inputNode.value).to.equal(''); + const { inputNode } = getProtectedMembers(el); + expect(inputNode.value).to.equal(''); /** * @param {'none' | 'list' | 'inline' | 'both'} autocomplete @@ -937,7 +1008,7 @@ describe('lion-combobox', () => { el.setCheckedIndex(index); el.opened = false; await el.updateComplete; - expect(el._inputNode.value).to.equal(valueOnClose); + expect(inputNode.value).to.equal(valueOnClose); } await performChecks('none', 0, 'Artichoke'); @@ -961,7 +1032,9 @@ describe('lion-combobox', () => { Victoria Plum `)); - expect(el._inputNode.value).to.equal(''); + + const { inputNode } = getProtectedMembers(el); + expect(inputNode.value).to.equal(''); /** * @param {'none' | 'list' | 'inline' | 'both'} autocomplete @@ -977,7 +1050,7 @@ describe('lion-combobox', () => { el.setCheckedIndex(index); el.opened = false; await el.updateComplete; - expect(el._inputNode.value).to.equal(valueOnClose); + expect(inputNode.value).to.equal(valueOnClose); } await performChecks('none', 0, ''); @@ -1054,20 +1127,21 @@ describe('lion-combobox', () => { Victoria Plum `)); + const { inputNode } = getProtectedMembers(el); mimicUserTyping(el, 'ch'); await el.updateComplete; - expect(el._inputNode.value).to.equal('Chard'); - expect(el._inputNode.selectionStart).to.equal('Ch'.length); - expect(el._inputNode.selectionEnd).to.equal('Chard'.length); + expect(inputNode.value).to.equal('Chard'); + expect(inputNode.selectionStart).to.equal('Ch'.length); + expect(inputNode.selectionEnd).to.equal('Chard'.length); // Autocompletion happened. When we go backwards ('Ch[ard]' => 'Ch'), we should not // autocomplete to 'Chard' anymore. await mimicUserTypingAdvanced(el, ['Backspace']); await el.updateComplete; - expect(el._inputNode.value).to.equal('Ch'); // so not 'Chard' - expect(el._inputNode.selectionStart).to.equal('Ch'.length); - expect(el._inputNode.selectionEnd).to.equal('Ch'.length); + expect(inputNode.value).to.equal('Ch'); // so not 'Chard' + expect(inputNode.selectionStart).to.equal('Ch'.length); + expect(inputNode.selectionEnd).to.equal('Ch'.length); }); describe('Subclassers', () => { @@ -1133,27 +1207,29 @@ describe('lion-combobox', () => { Victoria Plum `)); - expect(el._inputNode.value).to.equal(''); + const { inputNode } = getProtectedMembers(el); + + expect(inputNode.value).to.equal(''); el.setCheckedIndex(-1); el.autocomplete = 'none'; el.setCheckedIndex(0); - expect(el._inputNode.value).to.equal(''); + expect(inputNode.value).to.equal(''); el.setCheckedIndex(-1); el.autocomplete = 'list'; el.setCheckedIndex(0); - expect(el._inputNode.value).to.equal(''); + expect(inputNode.value).to.equal(''); el.setCheckedIndex(-1); el.autocomplete = 'inline'; el.setCheckedIndex(0); - expect(el._inputNode.value).to.equal('Artichoke'); + expect(inputNode.value).to.equal('Artichoke'); el.setCheckedIndex(-1); el.autocomplete = 'both'; el.setCheckedIndex(0); - expect(el._inputNode.value).to.equal('Artichoke'); + expect(inputNode.value).to.equal('Artichoke'); }); it('synchronizes last index to textbox when autocomplete is "inline" or "both" when multipleChoice', async () => { @@ -1165,33 +1241,35 @@ describe('lion-combobox', () => { Victoria Plum `)); - expect(el._inputNode.value).to.eql(''); + const { inputNode } = getProtectedMembers(el); + + expect(inputNode.value).to.eql(''); el.setCheckedIndex(-1); el.autocomplete = 'none'; el.setCheckedIndex([0]); el.setCheckedIndex([1]); - expect(el._inputNode.value).to.equal(''); + expect(inputNode.value).to.equal(''); el.setCheckedIndex(-1); el.autocomplete = 'list'; el.setCheckedIndex([0]); el.setCheckedIndex([1]); - expect(el._inputNode.value).to.equal(''); + expect(inputNode.value).to.equal(''); el.setCheckedIndex(-1); el.autocomplete = 'inline'; el.setCheckedIndex([0]); - expect(el._inputNode.value).to.equal('Artichoke'); + expect(inputNode.value).to.equal('Artichoke'); el.setCheckedIndex([1]); - expect(el._inputNode.value).to.equal('Chard'); + expect(inputNode.value).to.equal('Chard'); el.setCheckedIndex(-1); el.autocomplete = 'both'; el.setCheckedIndex([0]); - expect(el._inputNode.value).to.equal('Artichoke'); + expect(inputNode.value).to.equal('Artichoke'); el.setCheckedIndex([1]); - expect(el._inputNode.value).to.equal('Chard'); + expect(inputNode.value).to.equal('Chard'); }); describe('Subclassers', () => { @@ -1224,26 +1302,27 @@ describe('lion-combobox', () => { Victoria Plum `)); + const { inputNode } = getProtectedMembers(el); el.setCheckedIndex(-1); el.autocomplete = 'none'; el.setCheckedIndex([0]); - expect(el._inputNode.value).to.equal('Artichoke--multi'); + expect(inputNode.value).to.equal('Artichoke--multi'); el.setCheckedIndex(-1); el.autocomplete = 'list'; el.setCheckedIndex([0]); - expect(el._inputNode.value).to.equal('Artichoke--multi'); + expect(inputNode.value).to.equal('Artichoke--multi'); el.setCheckedIndex(-1); el.autocomplete = 'inline'; el.setCheckedIndex([0]); - expect(el._inputNode.value).to.equal('Artichoke--multi'); + expect(inputNode.value).to.equal('Artichoke--multi'); el.setCheckedIndex(-1); el.autocomplete = 'both'; el.setCheckedIndex([0]); - expect(el._inputNode.value).to.equal('Artichoke--multi'); + expect(inputNode.value).to.equal('Artichoke--multi'); }); }); @@ -1277,6 +1356,7 @@ describe('lion-combobox', () => { Victoria Plum `)); + const { inputNode } = getProtectedMembers(el); /** * @param {LionCombobox} elm @@ -1304,7 +1384,7 @@ describe('lion-combobox', () => { expect(el.activeIndex).to.equal(-1); expect(el.opened).to.be.true; - mimicKeyPress(el._inputNode, 'Enter'); + mimicKeyPress(inputNode, 'Enter'); expect(el.opened).to.be.false; expect(el.activeIndex).to.equal(-1); @@ -1318,7 +1398,7 @@ describe('lion-combobox', () => { expect(el.opened).to.be.true; expect(el.activeIndex).to.equal(-1); - mimicKeyPress(el._inputNode, 'Enter'); + mimicKeyPress(inputNode, 'Enter'); expect(el.activeIndex).to.equal(-1); expect(el.opened).to.be.false; @@ -1335,7 +1415,7 @@ describe('lion-combobox', () => { expect(el.activeIndex).to.equal(1); - mimicKeyPress(el._inputNode, 'Enter'); + mimicKeyPress(inputNode, 'Enter'); await el.updateComplete; await el.updateComplete; @@ -1349,7 +1429,7 @@ describe('lion-combobox', () => { await el.updateComplete; mimicUserTyping(/** @type {LionCombobox} */ (el), 'cha'); await el.updateComplete; - mimicKeyPress(el._inputNode, 'Enter'); + mimicKeyPress(inputNode, 'Enter'); expect(el.activeIndex).to.equal(1); expect(el.opened).to.be.false; }); @@ -1363,6 +1443,8 @@ describe('lion-combobox', () => { Victoria Plum `)); + const { inputNode } = getProtectedMembers(el); + mimicUserTyping(/** @type {LionCombobox} */ (el), 'cha'); await el.updateComplete; expect(el.activeIndex).to.equal(1); @@ -1378,7 +1460,7 @@ describe('lion-combobox', () => { // select artichoke mimicUserTyping(/** @type {LionCombobox} */ (el), 'artichoke'); await el.updateComplete; - mimicKeyPress(el._inputNode, 'Enter'); + mimicKeyPress(inputNode, 'Enter'); mimicUserTyping(/** @type {LionCombobox} */ (el), ''); await el.updateComplete; @@ -1397,15 +1479,17 @@ describe('lion-combobox', () => { Victoria Plum `)); + const { inputNode } = getProtectedMembers(el); + // Select something mimicUserTyping(/** @type {LionCombobox} */ (el), 'cha'); await el.updateComplete; - mimicKeyPress(el._inputNode, 'Enter'); + mimicKeyPress(inputNode, 'Enter'); expect(el.activeIndex).to.equal(1); - mimicKeyPress(el._inputNode, 'Escape'); + mimicKeyPress(inputNode, 'Escape'); await el.updateComplete; - expect(el._inputNode.textContent).to.equal(''); + expect(inputNode.textContent).to.equal(''); el.formElements.forEach(option => expect(option.active).to.be.false); @@ -1419,19 +1503,21 @@ describe('lion-combobox', () => { describe('Accessibility', () => { it('synchronizes autocomplete option to textbox', async () => { let el; - [el] = await fruitFixture({ autocomplete: 'both' }); + // @ts-expect-error expect(el._inputNode.getAttribute('aria-autocomplete')).to.equal('both'); [el] = await fruitFixture({ autocomplete: 'list' }); + // @ts-expect-error expect(el._inputNode.getAttribute('aria-autocomplete')).to.equal('list'); [el] = await fruitFixture({ autocomplete: 'none' }); + // @ts-expect-error expect(el._inputNode.getAttribute('aria-autocomplete')).to.equal('none'); }); it('updates aria-activedescendant on textbox node', async () => { - let el = /** @type {LionCombobox} */ (await fixture(html` + const el = /** @type {LionCombobox} */ (await fixture(html` Artichoke Chard @@ -1440,20 +1526,26 @@ describe('lion-combobox', () => { `)); - expect(el._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal(null); + const elProts = getProtectedMembers(el); + + expect(elProts.activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( + null, + ); expect(el.formElements[1].active).to.equal(false); mimicUserTyping(/** @type {LionCombobox} */ (el), 'ch'); await el.updateComplete; - expect(el._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal(null); + expect(elProts.activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( + null, + ); // el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' })); - mimicKeyPress(el._inputNode, 'ArrowDown'); - expect(el._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( + mimicKeyPress(elProts.inputNode, 'ArrowDown'); + expect(elProts.activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( 'artichoke-option', ); expect(el.formElements[1].active).to.equal(false); - el = /** @type {LionCombobox} */ (await fixture(html` + const el2 = /** @type {LionCombobox} */ (await fixture(html` Artichoke Chard @@ -1461,20 +1553,23 @@ describe('lion-combobox', () => { Victoria Plum `)); - mimicUserTyping(/** @type {LionCombobox} */ (el), 'ch'); - await el.updateComplete; - expect(el._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( - el.formElements[1].id, - ); - expect(el.formElements[1].active).to.equal(true); - el.autocomplete = 'list'; - mimicUserTyping(/** @type {LionCombobox} */ (el), 'ch'); - await el.updateComplete; - expect(el._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( - el.formElements[1].id, + const el2Prots = getProtectedMembers(el2); + + mimicUserTyping(/** @type {LionCombobox} */ (el2), 'ch'); + await el2.updateComplete; + expect(el2Prots.activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( + el2.formElements[1].id, ); - expect(el.formElements[1].active).to.equal(true); + expect(el2.formElements[1].active).to.equal(true); + + el2.autocomplete = 'list'; + mimicUserTyping(/** @type {LionCombobox} */ (el), 'ch'); + await el2.updateComplete; + expect(el2Prots.activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( + el2.formElements[1].id, + ); + expect(el2.formElements[1].active).to.equal(true); }); it('adds aria-label to highlighted options', async () => { @@ -1496,7 +1591,9 @@ describe('lion-combobox', () => { Item 1 `)); - expect(el._comboboxNode.contains(el._inputNode)).to.be.true; + const { comboboxNode, inputNode } = getProtectedMembers(el); + + expect(comboboxNode.contains(inputNode)).to.be.true; }); it('has one input node with [role=combobox] in v1.0', async () => { @@ -1505,7 +1602,9 @@ describe('lion-combobox', () => { Item 1 `)); - expect(el._comboboxNode).to.equal(el._inputNode); + const { comboboxNode, inputNode } = getProtectedMembers(el); + + expect(comboboxNode).to.equal(inputNode); }); it('autodetects aria version and sets it to 1.1 on Chromium browsers', async () => { @@ -1517,7 +1616,9 @@ describe('lion-combobox', () => { Item 1 `)); - expect(el._ariaVersion).to.equal('1.1'); + const elProts = getProtectedMembers(el); + + expect(elProts.ariaVersion).to.equal('1.1'); browserDetection.isChromium = false; const el2 = /** @type {LionCombobox} */ (await fixture(html` @@ -1525,7 +1626,9 @@ describe('lion-combobox', () => { Item 1 `)); - expect(el2._ariaVersion).to.equal('1.0'); + const el2Prots = getProtectedMembers(el2); + + expect(el2Prots.ariaVersion).to.equal('1.0'); // restore... browserDetection.isChromium = browserDetectionIsChromiumOriginal; @@ -1545,8 +1648,10 @@ describe('lion-combobox', () => { `)); + const { comboboxNode } = getProtectedMembers(el); + // activate opened listbox - el._comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); mimicUserTyping(el, 'ch'); await el.updateComplete; diff --git a/packages/core/src/DelegateMixin.js b/packages/core/src/DelegateMixin.js index a8f248f13..1fe554d0f 100644 --- a/packages/core/src/DelegateMixin.js +++ b/packages/core/src/DelegateMixin.js @@ -22,11 +22,18 @@ const DelegateMixinImplementation = superclass => constructor() { super(); - /** @type {DelegateEvent[]} */ + /** + * @type {DelegateEvent[]} + * @private + */ this.__eventsQueue = []; - /** @type {Object.} */ + /** + * @type {Object.} + * @private + */ this.__propertiesQueue = {}; + /** @private */ this.__setupPropertyDelegation(); } @@ -101,6 +108,9 @@ const DelegateMixinImplementation = superclass => super.removeAttribute(name); } + /** + * @protected + */ _connectDelegateMixin() { if (this.__connectedDelegateMixin) return; @@ -117,6 +127,9 @@ const DelegateMixinImplementation = superclass => } } + /** + * @private + */ __setupPropertyDelegation() { const propertyNames = this.delegations.properties.concat(this.delegations.methods); propertyNames.forEach(propertyName => { @@ -153,6 +166,9 @@ const DelegateMixinImplementation = superclass => }); } + /** + * @private + */ __initialAttributeDelegation() { const attributeNames = this.delegations.attributes; attributeNames.forEach(attributeName => { @@ -164,12 +180,18 @@ const DelegateMixinImplementation = superclass => }); } + /** + * @private + */ __emptyEventListenerQueue() { this.__eventsQueue.forEach(ev => { this.delegationTarget.addEventListener(ev.type, ev.handler, ev.opts); }); } + /** + * @private + */ __emptyPropertiesQueue() { Object.keys(this.__propertiesQueue).forEach(propName => { this.delegationTarget[propName] = this.__propertiesQueue[propName]; diff --git a/packages/core/src/DisabledMixin.js b/packages/core/src/DisabledMixin.js index cfd4f5846..b080b6872 100644 --- a/packages/core/src/DisabledMixin.js +++ b/packages/core/src/DisabledMixin.js @@ -22,8 +22,11 @@ const DisabledMixinImplementation = superclass => constructor() { super(); + /** @protected */ this._requestedToBeDisabled = false; + /** @private */ this.__isUserSettingDisabled = true; + /** @private */ this.__restoreDisabledTo = false; this.disabled = false; } @@ -43,7 +46,10 @@ const DisabledMixinImplementation = superclass => } } - /** @param {boolean} value */ + /** + * @param {boolean} value + * @private + */ __internalSetDisabled(value) { this.__isUserSettingDisabled = false; this.disabled = value; diff --git a/packages/core/src/DisabledWithTabIndexMixin.js b/packages/core/src/DisabledWithTabIndexMixin.js index 04cf21462..316dd8c99 100644 --- a/packages/core/src/DisabledWithTabIndexMixin.js +++ b/packages/core/src/DisabledWithTabIndexMixin.js @@ -27,7 +27,9 @@ const DisabledWithTabIndexMixinImplementation = superclass => constructor() { super(); + /** @private */ this.__isUserSettingTabIndex = true; + /** @private */ this.__restoreTabIndexTo = 0; this.__internalSetTabIndex(0); } @@ -48,6 +50,7 @@ const DisabledWithTabIndexMixinImplementation = superclass => /** * @param {number} value + * @private */ __internalSetTabIndex(value) { this.__isUserSettingTabIndex = false; diff --git a/packages/core/src/SlotMixin.js b/packages/core/src/SlotMixin.js index d537b310e..0b51ee54b 100644 --- a/packages/core/src/SlotMixin.js +++ b/packages/core/src/SlotMixin.js @@ -22,6 +22,7 @@ const SlotMixinImplementation = superclass => constructor() { super(); + /** @private */ this.__privateSlots = new Set(null); } @@ -34,6 +35,9 @@ const SlotMixinImplementation = superclass => this._connectSlotMixin(); } + /** + * @protected + */ _connectSlotMixin() { if (!this.__isConnectedSlotMixin) { Object.keys(this.slots).forEach(slotName => { @@ -55,6 +59,7 @@ const SlotMixinImplementation = superclass => /** * @param {string} slotName Name of the slot * @return {boolean} true if given slot name been created by SlotMixin + * @protected */ _isPrivateSlot(slotName) { return this.__privateSlots.has(slotName); diff --git a/packages/dialog/src/LionDialog.js b/packages/dialog/src/LionDialog.js index 909875989..e94f8c7ea 100644 --- a/packages/dialog/src/LionDialog.js +++ b/packages/dialog/src/LionDialog.js @@ -4,11 +4,15 @@ import { OverlayMixin, withModalDialogConfig } from '@lion/overlays'; export class LionDialog extends OverlayMixin(LitElement) { constructor() { super(); + /** @private */ this.__toggle = () => { this.opened = !this.opened; }; } + /** + * @protected + */ // eslint-disable-next-line class-methods-use-this _defineOverlayConfig() { return { @@ -16,6 +20,9 @@ export class LionDialog extends OverlayMixin(LitElement) { }; } + /** + * @protected + */ _setupOpenCloseListeners() { super._setupOpenCloseListeners(); if (this._overlayInvokerNode) { @@ -23,6 +30,9 @@ export class LionDialog extends OverlayMixin(LitElement) { } } + /** + * @protected + */ _teardownOpenCloseListeners() { super._teardownOpenCloseListeners(); if (this._overlayInvokerNode) { diff --git a/packages/fieldset/src/LionFieldset.js b/packages/fieldset/src/LionFieldset.js index 6a582e15a..89b4c7070 100644 --- a/packages/fieldset/src/LionFieldset.js +++ b/packages/fieldset/src/LionFieldset.js @@ -22,11 +22,15 @@ import { FormGroupMixin } from '@lion/form-core'; export class LionFieldset extends FormGroupMixin(LitElement) { constructor() { super(); - /** @override FormRegistrarMixin */ + /** + * @override FormRegistrarMixin + * @protected + */ this._isFormOrFieldset = true; /** * @type {'child' | 'choice-group' | 'fieldset'} * @override FormControlMixin + * @protected */ this._repropagationRole = 'fieldset'; } diff --git a/packages/form-core/src/FocusMixin.js b/packages/form-core/src/FocusMixin.js index d0f7bd634..39c4240fe 100644 --- a/packages/form-core/src/FocusMixin.js +++ b/packages/form-core/src/FocusMixin.js @@ -46,14 +46,23 @@ const FocusMixinImplementation = superclass => } } + /** + * @private + */ __onFocus() { this.focused = true; } + /** + * @private + */ __onBlur() { this.focused = false; } + /** + * @private + */ __registerEventsForFocusMixin() { /** * focus @@ -98,6 +107,9 @@ const FocusMixinImplementation = superclass => this._inputNode.addEventListener('focusout', this.__redispatchFocusout); } + /** + * @private + */ __teardownEventsForFocusMixin() { this._inputNode.removeEventListener( 'focus', diff --git a/packages/form-core/src/FormControlMixin.js b/packages/form-core/src/FormControlMixin.js index 4ce740082..0556a9890 100644 --- a/packages/form-core/src/FormControlMixin.js +++ b/packages/form-core/src/FormControlMixin.js @@ -274,16 +274,19 @@ const FormControlMixinImplementation = superclass => } } + /** @protected */ _triggerInitialModelValueChangedEvent() { this.__dispatchInitialModelValueChangedEvent(); } + /** @protected */ _enhanceLightDomClasses() { if (this._inputNode) { this._inputNode.classList.add('form-control'); } } + /** @protected */ _enhanceLightDomA11y() { const { _inputNode, _labelNode, _helpTextNode, _feedbackNode } = this; @@ -310,6 +313,7 @@ const FormControlMixinImplementation = superclass => * When boolean attribute data-label or data-description is found, * the slot element will be connected to the input via aria-labelledby or aria-describedby * @param {string[]} additionalSlots + * @protected */ _enhanceLightDomA11yForAdditionalSlots( additionalSlots = ['prefix', 'suffix', 'before', 'after'], @@ -335,6 +339,7 @@ const FormControlMixinImplementation = superclass => * @param {string} attrName * @param {HTMLElement[]} nodes * @param {boolean|undefined} reorder + * @private */ __reflectAriaAttr(attrName, nodes, reorder) { if (this._inputNode) { @@ -393,6 +398,7 @@ const FormControlMixinImplementation = superclass => /** * @return {TemplateResult} + * @protected */ _groupOneTemplate() { return html` ${this._labelTemplate()} ${this._helpTextTemplate()} `; @@ -400,6 +406,7 @@ const FormControlMixinImplementation = superclass => /** * @return {TemplateResult} + * @protected */ _groupTwoTemplate() { return html` ${this._inputGroupTemplate()} ${this._feedbackTemplate()} `; @@ -407,6 +414,7 @@ const FormControlMixinImplementation = superclass => /** * @return {TemplateResult} + * @protected */ // eslint-disable-next-line class-methods-use-this _labelTemplate() { @@ -419,6 +427,7 @@ const FormControlMixinImplementation = superclass => /** * @return {TemplateResult} + * @protected */ // eslint-disable-next-line class-methods-use-this _helpTextTemplate() { @@ -431,6 +440,7 @@ const FormControlMixinImplementation = superclass => /** * @return {TemplateResult} + * @protected */ _inputGroupTemplate() { return html` @@ -447,6 +457,7 @@ const FormControlMixinImplementation = superclass => /** * @return {TemplateResult} + * @protected */ // eslint-disable-next-line class-methods-use-this _inputGroupBeforeTemplate() { @@ -459,6 +470,7 @@ const FormControlMixinImplementation = superclass => /** * @return {TemplateResult | nothing} + * @protected */ _inputGroupPrefixTemplate() { return !Array.from(this.children).find(child => child.slot === 'prefix') @@ -472,6 +484,7 @@ const FormControlMixinImplementation = superclass => /** * @return {TemplateResult} + * @protected */ // eslint-disable-next-line class-methods-use-this _inputGroupInputTemplate() { @@ -484,6 +497,7 @@ const FormControlMixinImplementation = superclass => /** * @return {TemplateResult | nothing} + * @protected */ _inputGroupSuffixTemplate() { return !Array.from(this.children).find(child => child.slot === 'suffix') @@ -497,6 +511,7 @@ const FormControlMixinImplementation = superclass => /** * @return {TemplateResult} + * @protected */ // eslint-disable-next-line class-methods-use-this _inputGroupAfterTemplate() { @@ -509,6 +524,7 @@ const FormControlMixinImplementation = superclass => /** * @return {TemplateResult} + * @protected */ // eslint-disable-next-line class-methods-use-this _feedbackTemplate() { @@ -522,6 +538,7 @@ const FormControlMixinImplementation = superclass => /** * @param {?} modelValue * @return {boolean} + * @protected */ // @ts-ignore FIXME: Move to FormatMixin? Since there we have access to modelValue prop _isEmpty(modelValue = this.modelValue) { @@ -675,6 +692,7 @@ const FormControlMixinImplementation = superclass => /** * @return {Array.} + * @protected */ // Returns dom references to all elements that should be referred to by field(s) _getAriaDescriptionElements() { @@ -727,6 +745,7 @@ const FormControlMixinImplementation = superclass => /** * @param {string} slotName * @return {HTMLElement | undefined} + * @private */ __getDirectSlotChild(slotName) { return /** @type {HTMLElement[]} */ (Array.from(this.children)).find( @@ -734,6 +753,7 @@ const FormControlMixinImplementation = superclass => ); } + /** @private */ __dispatchInitialModelValueChangedEvent() { // When we are not a fieldset / choice-group, we don't need to wait for our children // to send a unified event @@ -762,12 +782,14 @@ const FormControlMixinImplementation = superclass => /** * @param {CustomEvent} ev + * @protected */ // eslint-disable-next-line class-methods-use-this, no-unused-vars _onBeforeRepropagateChildrenValues(ev) {} /** * @param {CustomEvent} ev + * @private */ __repropagateChildrenValues(ev) { // Allows sub classes to internally listen to the children change events @@ -841,6 +863,7 @@ const FormControlMixinImplementation = superclass => * TODO: Extend this in choice group so that target is always a choice input and multipleChoice exists. * This will fix the types and reduce the need for ignores/expect-errors * @param {EventTarget & import('../types/choice-group/ChoiceInputMixinTypes').ChoiceInputHost} target + * @protected */ _repropagationCondition(target) { return !( @@ -861,6 +884,7 @@ const FormControlMixinImplementation = superclass => * _onLabelClick() { * this._invokerNode.focus(); * } + * @protected */ // eslint-disable-next-line class-methods-use-this _onLabelClick() {} diff --git a/packages/form-core/src/FormatMixin.js b/packages/form-core/src/FormatMixin.js index 86e299ded..dadb8e284 100644 --- a/packages/form-core/src/FormatMixin.js +++ b/packages/form-core/src/FormatMixin.js @@ -207,6 +207,7 @@ const FormatMixinImplementation = superclass => * @param {{source:'model'|'serialized'|'formatted'|null}} config - the type of value that triggered this method. It should not be * set again, so that its observer won't be triggered. Can be: * 'model'|'formatted'|'serialized'. + * @protected */ _calculateValues({ source } = { source: null }) { if (this.__preventRecursiveTrigger) return; // prevent infinite loops @@ -236,6 +237,7 @@ const FormatMixinImplementation = superclass => /** * @param {string|undefined} value * @return {?} + * @private */ __callParser(value = this.formattedValue) { // A) check if we need to parse at all @@ -273,6 +275,7 @@ const FormatMixinImplementation = superclass => /** * @returns {string|undefined} + * @private */ __callFormatter() { // - Why check for this.hasError? @@ -309,6 +312,7 @@ const FormatMixinImplementation = superclass => /** * Observer Handlers * @param {{ modelValue: unknown; }[]} args + * @protected */ _onModelValueChanged(...args) { this._calculateValues({ source: 'model' }); @@ -319,6 +323,7 @@ const FormatMixinImplementation = superclass => * @param {{ modelValue: unknown; }[]} args * This is wrapped in a distinct method, so that parents can control when the changed event * is fired. For objects, a deep comparison might be needed. + * @protected */ // eslint-disable-next-line no-unused-vars _dispatchModelValueChangedEvent(...args) { @@ -339,6 +344,7 @@ const FormatMixinImplementation = superclass => * Downwards syncing should only happen for `LionField`.value changes from 'above'. * This triggers _onModelValueChanged and connects user input * to the parsing/formatting/serializing loop. + * @protected */ _syncValueUpwards() { if (!this.__isHandlingComposition) { @@ -352,6 +358,7 @@ const FormatMixinImplementation = superclass => * - flow [1] will always be reflected back * - flow [2] will not be reflected back when this flow was triggered via * `@user-input-changed` (this will happen later, when `formatOn` condition is met) + * @protected */ _reflectBackFormattedValueToUser() { if (this._reflectBackOn()) { @@ -366,6 +373,7 @@ const FormatMixinImplementation = superclass => * call `super._reflectBackOn()` * @overridable * @return {boolean} + * @protected */ _reflectBackOn() { return !this.__isHandlingUserInput; @@ -375,6 +383,7 @@ const FormatMixinImplementation = superclass => // ("input" for or "change" for