diff --git a/packages/badge/src/Badge.ts b/packages/badge/src/Badge.ts
index 7c67e854b2..b16bb6e2ae 100644
--- a/packages/badge/src/Badge.ts
+++ b/packages/badge/src/Badge.ts
@@ -51,6 +51,18 @@ export class Badge extends SizedMixin(ObserveSlotText(SpectrumElement, '')) {
public variant: BadgeVariant = 'informative';
protected override render(): TemplateResult {
+ if (window.__swc.DEBUG) {
+ if (!BADGE_VARIANTS.includes(this.variant)) {
+ window.__swc.warn(
+ this,
+ `<${this.localName}> element expect the "variant" attribute to be one of the following:`,
+ 'https://opensource.adobe.com/spectrum-web-components/components/badge/#variants',
+ {
+ issues: [...BADGE_VARIANTS]
+ },
+ );
+ }
+ }
return html`
diff --git a/packages/badge/test/badge.test.ts b/packages/badge/test/badge.test.ts
index af764d3ae8..fe781e7446 100644
--- a/packages/badge/test/badge.test.ts
+++ b/packages/badge/test/badge.test.ts
@@ -14,6 +14,7 @@ import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
import '@spectrum-web-components/badge/sp-badge.js';
import '@spectrum-web-components/icons-workflow/icons/sp-icon-checkmark-circle.js';
+import { stub } from 'sinon';
import { Badge } from '../src/Badge.js';
import { testForLitDevWarnings } from '../../../test/testing-helpers.js';
@@ -32,6 +33,7 @@ describe('Badge', () => {
)
);
it('loads default badge accessibly', async () => {
+ const consoleWarnStub = stub(console, 'warn');
const el = await fixture
(
html`
@@ -46,5 +48,37 @@ describe('Badge', () => {
await elementUpdated(el);
await expect(el).to.be.accessible();
+ expect(consoleWarnStub.called).to.be.false;
+ consoleWarnStub.restore();
+ });
+ it('warns in Dev Mode when sent an incorrect `variant`', async () => {
+ const consoleWarnStub = stub(console, 'warn');
+ const el = await fixture(
+ html`
+
+
+ Icon and label
+
+ `
+ );
+
+ await elementUpdated(el);
+
+ expect(consoleWarnStub.called).to.be.true;
+ const spyCall = consoleWarnStub.getCall(0);
+ expect(
+ spyCall.args.at(0).includes('"variant"'),
+ 'confirm variant-centric message'
+ ).to.be.true;
+ expect(spyCall.args.at(-1), 'confirm `data` shape').to.deep.equal({
+ data: {
+ localName: 'sp-badge',
+ type: 'api',
+ level: 'default',
+ },
+ });
+ consoleWarnStub.restore();
});
});
diff --git a/packages/base/src/Base.ts b/packages/base/src/Base.ts
index 0483611083..d6d154c949 100644
--- a/packages/base/src/Base.ts
+++ b/packages/base/src/Base.ts
@@ -163,3 +163,52 @@ export function SpectrumMixin>(
}
export class SpectrumElement extends SpectrumMixin(LitElement) {}
+
+if (window.__swc.DEBUG) {
+ window.__swc = {
+ ...window.__swc,
+ issuedWarnings: new Set(),
+ warn: (element, message, url, { type = 'api', level = 'default', issues } = {}): void => {
+ const { localName = 'base' } = element || {};
+ const id = `${localName}:${type}:${level}` as BrandedSWCWarningID;
+ if (!window.__swc.verbose && window.__swc.issuedWarnings.has(id))
+ return;
+ window.__swc.issuedWarnings.add(id);
+ if (window.__swc.ignoreWarningLocalNames?.[localName]) return;
+ if (window.__swc.ignoreWarningTypes?.[type]) return;
+ if (window.__swc.ignoreWarningLevels?.[level]) return;
+ let listedIssues = '';
+ if (issues && issues.length) {
+ issues.unshift('');
+ listedIssues = issues.join('\n - ') + '\n';
+ }
+ const intro = level === 'deprecation' ? 'DEPRECATION NOTICE: ' : '';
+ const inspectElement = element
+ ? '\nInspect this issue in the follow element:'
+ : '';
+ const displayURL = (element ? '\n\n' : '\n') + url + '\n';
+ const messages: unknown[] = [];
+ messages.push(
+ intro + message + '\n' + listedIssues + inspectElement
+ );
+ if (element) {
+ messages.push(element);
+ }
+ messages.push(displayURL, {
+ data: {
+ localName,
+ type,
+ level,
+ }
+ });
+ console.warn(...messages);
+ },
+ };
+
+ window.__swc.warn(
+ undefined,
+ 'Spectrum Web Components is in dev mode. Not recommended for production!',
+ 'https://opensource.adobe.com/spectrum-web-components/dev-mode/',
+ { type: 'default' },
+ );
+}
diff --git a/packages/base/test/base-devmode.test.ts b/packages/base/test/base-devmode.test.ts
new file mode 100644
index 0000000000..dd69ff4858
--- /dev/null
+++ b/packages/base/test/base-devmode.test.ts
@@ -0,0 +1,37 @@
+/*
+Copyright 2020 Adobe. All rights reserved.
+This file is licensed to you under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License. You may obtain a copy
+of the License at http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software distributed under
+the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+OF ANY KIND, either express or implied. See the License for the specific language
+governing permissions and limitations under the License.
+*/
+import { expect } from '@open-wc/testing';
+import { stub } from 'sinon';
+
+describe('Base', () => {
+ it('warns in Dev Mode when no attributes', async () => {
+ const consoleWarnStub = stub(console, 'warn');
+ const { SpectrumElement } = await import(
+ '@spectrum-web-components/base'
+ );
+ expect(SpectrumElement).to.not.be.undefined;
+
+ expect(consoleWarnStub.called).to.be.true;
+ const spyCall = consoleWarnStub.getCall(0);
+ expect(
+ spyCall.args.at(0).includes('dev mode'),
+ 'confirm "dev mode"-centric message'
+ ).to.be.true;
+ expect(spyCall.args.at(-1), 'confirm `data` shape').to.deep.equal({
+ data: {
+ localName: 'base',
+ type: 'default',
+ level: 'default',
+ },
+ });
+ consoleWarnStub.restore();
+ });
+});
diff --git a/packages/card/sp-card.ts b/packages/card/sp-card.ts
index f6f80abcac..f8c0ee3a49 100644
--- a/packages/card/sp-card.ts
+++ b/packages/card/sp-card.ts
@@ -9,6 +9,7 @@ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTA
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
+
import { Card } from './src/Card.js';
customElements.define('sp-card', Card);
diff --git a/packages/menu/src/Menu.ts b/packages/menu/src/Menu.ts
index 4b652bb7ac..4b187f9708 100644
--- a/packages/menu/src/Menu.ts
+++ b/packages/menu/src/Menu.ts
@@ -22,8 +22,8 @@ import {
query,
} from '@spectrum-web-components/base/src/decorators.js';
-import {
- MenuItem,
+import { MenuItem } from './MenuItem.js';
+import type {
MenuItemAddedOrUpdatedEvent,
MenuItemRemovedEvent,
} from './MenuItem.js';
diff --git a/packages/overlay/src/loader.ts b/packages/overlay/src/loader.ts
index f92e8c4eb5..427dfaad11 100644
--- a/packages/overlay/src/loader.ts
+++ b/packages/overlay/src/loader.ts
@@ -18,6 +18,8 @@ export const openOverlay = async (
content: HTMLElement,
options: OverlayOptions
): Promise<() => void> => {
- const { Overlay } = await import('./overlay.js');
+ const { Overlay } = await import(
+ '@spectrum-web-components/overlay/src/overlay.js'
+ );
return Overlay.open(target, interaction, content, options);
};
diff --git a/packages/picker/src/Picker.ts b/packages/picker/src/Picker.ts
index bc8483cbb6..f730c4d4f6 100644
--- a/packages/picker/src/Picker.ts
+++ b/packages/picker/src/Picker.ts
@@ -278,19 +278,13 @@ export class PickerBase extends SizedMixin(Focusable) {
private popoverFragment!: DocumentFragment;
- private async generatePopover(deprecatedMenu: Menu | null): Promise {
+ private async generatePopover(): Promise {
if (!this.popoverFragment) {
this.popoverFragment = document.createDocumentFragment();
}
render(this.renderPopover, this.popoverFragment, { host: this });
this.popover = this.popoverFragment.children[0] as Popover;
this.optionsMenu = this.popover.children[1] as Menu;
-
- if (deprecatedMenu) {
- console.warn(
- `Deprecation Notice: You no longer need to provide an sp-menu child to ${this.tagName.toLowerCase()}. Any styling or attributes on the sp-menu will be ignored.`
- );
- }
}
private async openMenu(): Promise {
@@ -298,7 +292,7 @@ export class PickerBase extends SizedMixin(Focusable) {
let reparentableChildren: Element[] = [];
const deprecatedMenu = this.querySelector(':scope > sp-menu') as Menu;
- await this.generatePopover(deprecatedMenu);
+ await this.generatePopover();
if (deprecatedMenu) {
reparentableChildren = Array.from(deprecatedMenu.children);
} else {
@@ -457,6 +451,17 @@ export class PickerBase extends SizedMixin(Focusable) {
if (changes.has('value') && !changes.has('selectedItem')) {
this.updateMenuItems();
}
+ if (window.__swc.DEBUG) {
+ if (!this.hasUpdated && this.querySelector('sp-menu')) {
+ const { localName } = this;
+ window.__swc.warn(
+ this,
+ `You no longer need to provide an child to ${localName}. Any styling or attributes on the will be ignored.`,
+ 'https://opensource.adobe.com/spectrum-web-components/components/picker/#sizes',
+ { level: 'deprecation' },
+ );
+ }
+ }
super.update(changes);
}
diff --git a/packages/picker/test/index.ts b/packages/picker/test/index.ts
index 36fcd80a4d..51c381f253 100644
--- a/packages/picker/test/index.ts
+++ b/packages/picker/test/index.ts
@@ -10,7 +10,7 @@ OF ANY KIND, either express or implied. See the License for the specific languag
governing permissions and limitations under the License.
*/
-import type { Picker } from '..';
+import type { Picker } from '@spectrum-web-components/picker';
import type { OverlayOpenCloseDetail } from '@spectrum-web-components/overlay';
import type { MenuItem } from '@spectrum-web-components/menu';
@@ -24,7 +24,7 @@ import {
waitUntil,
} from '@open-wc/testing';
import '@spectrum-web-components/shared/src/focus-visible.js';
-import { spy } from 'sinon';
+import { spy, stub } from 'sinon';
import {
arrowDownEvent,
arrowLeftEvent,
@@ -1047,38 +1047,79 @@ export function runPickerTests(): void {
return test.querySelector('sp-picker') as Picker;
};
- beforeEach(async () => {
- el = await pickerFixture();
- await elementUpdated(el);
- });
- afterEach(async () => {
- if (el.open) {
- const closed = oneEvent(el, 'sp-closed');
- el.open = false;
- await closed;
- }
+ describe('Dev mode', () => {
+ it('warns in Dev Mode of deprecated `` usage', async () => {
+ const consoleWarnStub = stub(console, 'warn');
+ el = await pickerFixture();
+ await elementUpdated(el);
+
+ expect(consoleWarnStub.called).to.be.true;
+ const spyCall = consoleWarnStub.getCall(0);
+ expect(
+ spyCall.args.at(0).includes(''),
+ 'confirm -centric message'
+ ).to.be.true;
+ expect(
+ spyCall.args.at(-1),
+ 'confirm `data` shape'
+ ).to.deep.equal({
+ data: {
+ localName: 'sp-picker',
+ type: 'api',
+ level: 'deprecation',
+ },
+ });
+ consoleWarnStub.restore();
+ if (el.open) {
+ const closed = oneEvent(el, 'sp-closed');
+ el.open = false;
+ await closed;
+ }
+ });
});
- it('selects with deprecated syntax', async () => {
- const secondItem = el.querySelector(
- 'sp-menu-item:nth-of-type(2)'
- ) as MenuItem;
+ describe('Dev mode ignored', () => {
+ const { ignoreWarningLocalNames } = window.__swc;
+ before(() => {
+ window.__swc.ignoreWarningLocalNames = {
+ 'sp-picker': true,
+ };
+ });
+ before(() => {
+ window.__swc.ignoreWarningLocalNames = ignoreWarningLocalNames;
+ });
+ beforeEach(async () => {
+ el = await pickerFixture();
+ await elementUpdated(el);
+ });
+ afterEach(async () => {
+ if (el.open) {
+ const closed = oneEvent(el, 'sp-closed');
+ el.open = false;
+ await closed;
+ }
+ });
+ it('selects with deprecated syntax', async () => {
+ const secondItem = el.querySelector(
+ 'sp-menu-item:nth-of-type(2)'
+ ) as MenuItem;
- const opened = oneEvent(el, 'sp-opened');
- el.button.click();
- await opened;
- await elementUpdated(el);
+ const opened = oneEvent(el, 'sp-opened');
+ el.button.click();
+ await opened;
+ await elementUpdated(el);
- expect(el.open).to.be.true;
- expect(el.selectedItem?.itemText).to.be.undefined;
- expect(el.value).to.equal('');
+ expect(el.open).to.be.true;
+ expect(el.selectedItem?.itemText).to.be.undefined;
+ expect(el.value).to.equal('');
- const closed = oneEvent(el, 'sp-closed');
- secondItem.click();
- await closed;
+ const closed = oneEvent(el, 'sp-closed');
+ secondItem.click();
+ await closed;
- expect(el.open).to.be.false;
- expect(el.selectedItem?.itemText).to.equal('Select Inverse');
- expect(el.value).to.equal('option-2');
+ expect(el.open).to.be.false;
+ expect(el.selectedItem?.itemText).to.equal('Select Inverse');
+ expect(el.value).to.equal('option-2');
+ });
});
});
testForLitDevWarnings(async () => await pickerFixture());
diff --git a/packages/progress-bar/src/ProgressBar.ts b/packages/progress-bar/src/ProgressBar.ts
index 99fc081464..7bf0e44b82 100644
--- a/packages/progress-bar/src/ProgressBar.ts
+++ b/packages/progress-bar/src/ProgressBar.ts
@@ -100,5 +100,27 @@ export class ProgressBar extends SizedMixin(SpectrumElement) {
if (this.label && changes.has('label')) {
this.setAttribute('aria-label', this.label);
}
+
+ if (window.__swc.DEBUG) {
+ if (
+ !this.label &&
+ !this.getAttribute('aria-label') &&
+ !this.getAttribute('aria-labelledby')
+ ) {
+ window.__swc.warn(
+ this,
+ ' elements will not be accessible to screen readers without one of the following:',
+ 'https://opensource.adobe.com/spectrum-web-components/components/progress-bar/#accessibility',
+ {
+ type: 'accessibility',
+ issues: [
+ 'value supplied to the "label" attribute, which will be displayed visually as part of the element, or',
+ 'value supplied to the "aria-label" attribute, which will only be provided to screen readers, or',
+ 'an element ID reference supplied to the "aria-labelledby" attribute, which will be provided by screen readers and will need to be managed manually by the parent application.',
+ ]
+ },
+ );
+ }
+ }
}
}
diff --git a/packages/progress-bar/test/progress-bar.test.ts b/packages/progress-bar/test/progress-bar.test.ts
index 2e0f803ad5..81bba7a19b 100644
--- a/packages/progress-bar/test/progress-bar.test.ts
+++ b/packages/progress-bar/test/progress-bar.test.ts
@@ -14,6 +14,7 @@ import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
import '@spectrum-web-components/progress-bar/sp-progress-bar.js';
import { ProgressBar } from '@spectrum-web-components/progress-bar';
+import { stub } from 'sinon';
import { testForLitDevWarnings } from '../../../test/testing-helpers.js';
describe('ProgressBar', () => {
@@ -103,4 +104,27 @@ describe('ProgressBar', () => {
expect(el.hasAttribute('aria-valuenow')).to.be.false;
});
+ it('warns in Dev Mode when accessible attributes are not leveraged', async () => {
+ const consoleWarnStub = stub(console, 'warn');
+ const el = await fixture(html`
+
+ `);
+
+ await elementUpdated(el);
+
+ expect(consoleWarnStub.called).to.be.true;
+ const spyCall = consoleWarnStub.getCall(0);
+ expect(
+ spyCall.args.at(0).includes('accessible'),
+ 'confirm accessibility-centric message'
+ ).to.be.true;
+ expect(spyCall.args.at(-1), 'confirm `data` shape').to.deep.equal({
+ data: {
+ localName: 'sp-progress-bar',
+ type: 'accessibility',
+ level: 'default',
+ },
+ });
+ consoleWarnStub.restore();
+ });
});
diff --git a/packages/progress-circle/src/ProgressCircle.ts b/packages/progress-circle/src/ProgressCircle.ts
index 505d011503..6f150448c9 100644
--- a/packages/progress-circle/src/ProgressCircle.ts
+++ b/packages/progress-circle/src/ProgressCircle.ts
@@ -95,5 +95,27 @@ export class ProgressCircle extends SizedMixin(SpectrumElement, {
if (this.label && changes.has('label')) {
this.setAttribute('aria-label', this.label);
}
+
+ if (window.__swc.DEBUG) {
+ if (
+ !this.label &&
+ !this.getAttribute('aria-label') &&
+ !this.getAttribute('aria-labelledby')
+ ) {
+ window.__swc.warn(
+ this,
+ ' elements will not be accessible to screen readers without one of the following:',
+ 'https://opensource.adobe.com/spectrum-web-components/components/progress-circle/#accessibility',
+ {
+ type: 'accessibility',
+ issues: [
+ 'value supplied to the "label" attribute, which will be displayed visually as part of the element, or',
+ 'value supplied to the "aria-label" attribute, which will only be provided to screen readers, or',
+ 'an element ID reference supplied to the "aria-labelledby" attribute, which will be provided by screen readers and will need to be managed manually by the parent application.',
+ ]
+ },
+ );
+ }
+ }
}
}
diff --git a/packages/progress-circle/test/progress-circle.test.ts b/packages/progress-circle/test/progress-circle.test.ts
index 42bb2d54a9..6c13a88f6b 100644
--- a/packages/progress-circle/test/progress-circle.test.ts
+++ b/packages/progress-circle/test/progress-circle.test.ts
@@ -14,6 +14,7 @@ import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
import '@spectrum-web-components/progress-circle/sp-progress-circle.js';
import { ProgressCircle } from '@spectrum-web-components/progress-circle';
+import { stub } from 'sinon';
import { testForLitDevWarnings } from '../../../test/testing-helpers.js';
describe('ProgressCircle', () => {
@@ -77,4 +78,27 @@ describe('ProgressCircle', () => {
expect(el.hasAttribute('aria-valuenow')).to.be.false;
});
+ it('warns in Dev Mode when accessible attributes are not leveraged', async () => {
+ const consoleWarnStub = stub(console, 'warn');
+ const el = await fixture(html`
+
+ `);
+
+ await elementUpdated(el);
+
+ expect(consoleWarnStub.called).to.be.true;
+ const spyCall = consoleWarnStub.getCall(0);
+ expect(
+ spyCall.args.at(0).includes('accessible'),
+ 'confirm accessibility-centric message'
+ ).to.be.true;
+ expect(spyCall.args.at(-1), 'confirm `data` shape').to.deep.equal({
+ data: {
+ localName: 'sp-progress-circle',
+ type: 'accessibility',
+ level: 'default',
+ },
+ });
+ consoleWarnStub.restore();
+ });
});
diff --git a/packages/slider/slider-handle.md b/packages/slider/slider-handle.md
index 8b371d152d..afb14561b5 100644
--- a/packages/slider/slider-handle.md
+++ b/packages/slider/slider-handle.md
@@ -51,7 +51,7 @@ This examples uses the `"range"` variant along with two handles to create a rang
## Multi-handle Slider with Ordered Handles
-For slider handles that have the same numeric range, you can specify `min="previous"` or `max="next"` to constrain handles by the values of their neighbours.
+For slider handles that have the same numeric range, you can specify `min="previous"` or `max="next"` to constrain handles by the values of their `previous/nextElementSiblings`. Keep in mind that the _first_ slider handle with not have a `previous` handle to be its `min` and the _last_ slider handle will not have a `next` handle to be its `max`.
```html
diff --git a/packages/slider/src/HandleController.ts b/packages/slider/src/HandleController.ts
index de0b74ba2c..75e3d5366c 100644
--- a/packages/slider/src/HandleController.ts
+++ b/packages/slider/src/HandleController.ts
@@ -589,11 +589,15 @@ export class HandleController implements Controller {
previous.value,
result.range.min
);
- /* c8 ignore next 5 */
- } else {
- console.warn(
- 'First slider handle cannot have attribute min="previous"'
- );
+ }
+ if (window.__swc.DEBUG) {
+ if (!previous) {
+ window.__swc.warn(
+ this.host,
+ ' elements that are the first child of an element cannot have attribute "min=\'previous\'"`',
+ 'https://opensource.adobe.com/spectrum-web-components/components/slider-handle/#multi-handle-slider-with-ordered-handles'
+ );
+ }
}
}
if (handle.max === 'next') {
@@ -606,11 +610,15 @@ export class HandleController implements Controller {
}
}
result.clamp.max = Math.min(next.value, result.range.max);
- /* c8 ignore next 5 */
- } else {
- console.warn(
- 'Last slider handle cannot have attribute max="next"'
- );
+ }
+ if (window.__swc.DEBUG) {
+ if (!next) {
+ window.__swc.warn(
+ this.host,
+ ' elements that are the last child of an element cannot have attribute "max=\'next\'"',
+ 'https://opensource.adobe.com/spectrum-web-components/components/slider-handle/#multi-handle-slider-with-ordered-handles'
+ );
+ }
}
}
return result;
diff --git a/packages/slider/test/slider.test.ts b/packages/slider/test/slider.test.ts
index aa9ec302b2..d6934d3fd5 100644
--- a/packages/slider/test/slider.test.ts
+++ b/packages/slider/test/slider.test.ts
@@ -26,6 +26,7 @@ import {
import { sendKeys } from '@web/test-runner-commands';
import { ProvideLang } from '@spectrum-web-components/theme';
import { sendMouse } from '../../../test/plugins/browser.js';
+import { stub } from 'sinon';
import { testForLitDevWarnings } from '../../../test/testing-helpers.js';
describe('Slider', () => {
@@ -1073,6 +1074,104 @@ describe('Slider', () => {
await elementUpdated(el);
expect(el.values).to.deep.equal({ a: 10, b: 10, c: 10 });
});
+ it('warns in Dev Mode when `min="previous"` is leveraged on first handle', async () => {
+ const consoleWarnStub = stub(console, 'warn');
+ window.__swc.issuedWarnings = new Set();
+ const el = await fixture(
+ html`
+
+
+
+
+
+ `
+ );
+
+ await elementUpdated(el);
+
+ expect(consoleWarnStub.called).to.be.true;
+ const spyCall = consoleWarnStub.getCall(0);
+ expect(
+ spyCall.args.at(0).includes('previous'),
+ 'confirm "previous" in message'
+ ).to.be.true;
+ expect(spyCall.args.at(-1), 'confirm `data` shape').to.deep.equal({
+ data: {
+ localName: 'sp-slider',
+ type: 'api',
+ level: 'default',
+ },
+ });
+ consoleWarnStub.restore();
+ });
+ it('warns in Dev Mode when `max="next"` is leveraged on last handle', async () => {
+ const consoleWarnStub = stub(console, 'warn');
+ window.__swc.issuedWarnings = new Set();
+ const el = await fixture(
+ html`
+
+
+
+
+
+ `
+ );
+
+ await elementUpdated(el);
+
+ expect(consoleWarnStub.called).to.be.true;
+ const spyCall = consoleWarnStub.getCall(0);
+ expect(spyCall.args.at(0).includes('next'), 'confirm "next" in message')
+ .to.be.true;
+ expect(spyCall.args.at(-1), 'confirm `data` shape').to.deep.equal({
+ data: {
+ localName: 'sp-slider',
+ type: 'api',
+ level: 'default',
+ },
+ });
+ consoleWarnStub.restore();
+ });
it('builds both handles from a ', async () => {
const template = document.createElement('template');
template.innerHTML = `
diff --git a/packages/textfield/sp-textfield.ts b/packages/textfield/sp-textfield.ts
index 78061c84b3..d71f311593 100644
--- a/packages/textfield/sp-textfield.ts
+++ b/packages/textfield/sp-textfield.ts
@@ -9,6 +9,7 @@ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTA
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
+
import { Textfield } from './src/Textfield.js';
customElements.define('sp-textfield', Textfield);
diff --git a/packages/theme/README.md b/packages/theme/README.md
index 6f598e8ca1..1fd0bb0a8f 100644
--- a/packages/theme/README.md
+++ b/packages/theme/README.md
@@ -54,6 +54,23 @@ import '@spectrum-web-components/theme/express/scale-medium.js';
import '@spectrum-web-components/theme/express/scale-large.js';
```
+## Example
+
+An `` element expects a value for each of its `theme`, `color`, and `scale` attributes to be provided on the element.
+
+```html
+
+
+ Click me!
+
+
+```
+
## Quick start
```
diff --git a/packages/theme/src/Theme.ts b/packages/theme/src/Theme.ts
index 220104e934..2cb193e451 100644
--- a/packages/theme/src/Theme.ts
+++ b/packages/theme/src/Theme.ts
@@ -289,6 +289,49 @@ export class Theme extends HTMLElement implements ThemeKindProvider {
}
return acc;
}, [] as CSSResultGroup[]);
+ if (window.__swc.DEBUG) {
+ const issues: string[] = [];
+ const checkForAttribute = (
+ name: FragmentType,
+ resolvedValue?: string,
+ actualValue?: string
+ ): void => {
+ const themeModifier =
+ this.theme && this.theme !== 'spectrum'
+ ? `-${this.theme}`
+ : '';
+ if (!resolvedValue) {
+ issues.push(
+ `You have not explicitly set the "${name}" attribute and there is no default value on which to fallback.`
+ );
+ } else if (!actualValue) {
+ issues.push(
+ `You have not explicitly set the "${name}" attribute, the default value ("${resolvedValue}") is being used as a fallback.`
+ );
+ } else if (
+ !Theme.themeFragmentsByKind
+ .get(name)
+ ?.get(resolvedValue + themeModifier)
+ ) {
+ issues.push(
+ `You have set "${name}='${resolvedValue}'" but the associated theme fragment has not been loaded.`
+ );
+ }
+ };
+ checkForAttribute('theme', this.theme, this._theme);
+ checkForAttribute('color', this.color, this._color);
+ checkForAttribute('scale', this.scale, this._scale);
+ if (issues.length) {
+ window.__swc.warn(
+ this,
+ 'You are leveraging an element and the following issues may disrupt your theme delivery:',
+ 'https://opensource.adobe.com/spectrum-web-components/components/theme/#example',
+ {
+ issues
+ },
+ );
+ }
+ }
return [...styles];
}
diff --git a/packages/theme/test/theme-devmode.test.ts b/packages/theme/test/theme-devmode.test.ts
new file mode 100644
index 0000000000..b8fd80db45
--- /dev/null
+++ b/packages/theme/test/theme-devmode.test.ts
@@ -0,0 +1,45 @@
+/*
+Copyright 2020 Adobe. All rights reserved.
+This file is licensed to you under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License. You may obtain a copy
+of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software distributed under
+the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+OF ANY KIND, either express or implied. See the License for the specific language
+governing permissions and limitations under the License.
+*/
+
+import '@spectrum-web-components/theme/sp-theme.js';
+import { Theme } from '@spectrum-web-components/theme';
+import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
+import { stub } from 'sinon';
+
+describe('Dev mode', () => {
+ window.__swc.verbose = true;
+ it('warns in Dev Mode when no attributes or fragments', async () => {
+ const consoleWarnStub = stub(console, 'warn');
+ const el = await fixture(
+ html`
+
+ `
+ );
+
+ await elementUpdated(el);
+
+ expect(consoleWarnStub.called).to.be.true;
+ const spyCall = consoleWarnStub.getCall(0);
+ expect(
+ spyCall.args.at(0).includes('theme delivery'),
+ 'confirm "theme delivery"-centric message'
+ ).to.be.true;
+ expect(spyCall.args.at(-1), 'confirm `data` shape').to.deep.equal({
+ data: {
+ localName: 'sp-theme',
+ type: 'api',
+ level: 'default',
+ },
+ });
+ consoleWarnStub.restore();
+ });
+});
diff --git a/projects/types/global.d.ts b/projects/types/global.d.ts
index 36f1f7d74c..361b96028e 100644
--- a/projects/types/global.d.ts
+++ b/projects/types/global.d.ts
@@ -10,14 +10,16 @@ OF ANY KIND, either express or implied. See the License for the specific languag
governing permissions and limitations under the License.
*/
+type ElementLocalName = string;
+
type WarningType = 'default' | 'accessibility' | 'api';
type WarningLevel = 'default' | 'low' | 'medium' | 'heigh' | 'deprecation';
-type SWCWarningData = {
- localName: string;
- type: WarningType;
- level: WarningLevel;
+type SWCWarningOptions = {
+ type?: WarningType;
+ level?: WarningLevel;
+ issues?: string[];
};
type BrandedSWCWarningID = `${ElementLocalName}:${WarningType}:${WarningLevel}`;
@@ -33,12 +35,11 @@ interface Window {
* @param url {string} - a URL at which more infromation, or the standard documentation, can be found
* @param issues {string[]} - an optional array of issues to format into the message
*/
- issueWarning(
+ warn(
element: HTMLElement | undefined,
- warningData: SWCWarningData,
- warning: string,
+ message: string,
url: string,
- issues?: string[]
+ options?: SWCWarningOptions
): void;
issuedWarnings: Set;
ignoreWarningTypes: Record;