diff --git a/README.md b/README.md index fb8843d6..09ebd43f 100644 --- a/README.md +++ b/README.md @@ -114,17 +114,35 @@ The addon provides 1 Glimmer component and 3 helpers:
<ContainerQuery> -The component uses `...attributes` so that you can pass `class` or [`local-class`](https://github.com/salsify/ember-css-modules) _for styling_.1 +### Arguments -It also accepts these arguments: +You can pass these arguments to the component. | Name | Required | Description | Type | |--|:--:|--|--| -| @features | Yes2 | Container query definitions | POJO | +| @features | Yes1 | Container query definitions | POJO | | @dataAttributePrefix | No | Prefix for data attributes | string | | @debounce | No | Debounce time for resize (ms) | number ≥ 0 | +| @tagName | No | Container tag name2 | HTML tag name | -The component returns a few values that you can consume: +1. The component renders without error when `@features` isn't provided. In practice, you will always want to set `@features`. + +2. By default, the component is a `
` element. You can pass a valid HTML tag name to facilitate accessibility and semantic HTML. + +### Attributes + +You _may_1 pass attributes to the component for these reasons: + +- Style (e.g. `class`, [`local-class`](https://github.com/salsify/ember-css-modules)) +- Accessibility (e.g. ARIA attributes2, roles) + +1. Do refrain from overusing splattributes (e.g. pass a `{{did-insert}}` modifier to fetch data), since the component's API may change and cause unexpected results. Practice separation of concerns when possible. For example, data fetching can be handled by another element or [`@use` decorator](https://github.com/emberjs/rfcs/blob/use-and-resources/text/0567-use-and-resources.md). + +2. When an [ARIA attribute has multiple values](https://github.com/ijlee2/ember-container-query/issues/38#issuecomment-647017665), the order of values can matter. At the moment, splattributes doesn't guarantee the order. + +### Outputs + +You can consume these values in your app or addon. | Name | Yielded | Description | Type | |--|:--:|--|--| @@ -132,16 +150,14 @@ The component returns a few values that you can consume: | dimensions | Yes | Container dimensions | POJO | | data-container-query-_{featureName}_ | No | Data attributes for CSS selector | HTML data attribute | -1. Do refrain from overusing splattributes (e.g. pass a `{{did-insert}}` modifier to fetch data), since the component's API may change and cause unexpected results. Practice separation of concerns when possible. For example, data fetching can be handled by another element or [`@use` decorator](https://github.com/emberjs/rfcs/blob/use-and-resources/text/0567-use-and-resources.md). - -2. The component renders without error when `@features` isn't provided. In practice, you will always want to set `@features`. -
{{cq-aspect-ratio}}, {{cq-height}}, {{cq-width}} +### Arguments + All helpers accept these arguments: | Name | Required | Description | Type | diff --git a/addon/components/container-query.hbs b/addon/components/container-query.hbs index e9191a38..83d4a3a2 100644 --- a/addon/components/container-query.hbs +++ b/addon/components/container-query.hbs @@ -1,16 +1,18 @@ -
- {{yield (hash - features=this.queryResults - dimensions=(hash - aspectRatio=this.aspectRatio - height=this.height - width=this.width - ) - )}} -
\ No newline at end of file +{{#let (element this.tagName) as |Tag|}} + + {{yield (hash + features=this.queryResults + dimensions=(hash + aspectRatio=this.aspectRatio + height=this.height + width=this.width + ) + )}} + +{{/let}} \ No newline at end of file diff --git a/addon/components/container-query.js b/addon/components/container-query.js index 50cc38d6..12080105 100644 --- a/addon/components/container-query.js +++ b/addon/components/container-query.js @@ -20,6 +20,13 @@ export default class ContainerQueryComponent extends Component { return this.args.debounce ?? 0; } + constructor() { + super(...arguments); + + // The dynamic tag is restricted to be immutable + this.tagName = this.args.tagName || 'div'; + } + @action queryContainer(element) { this.measureDimensions(element); this.evaluateQueries(); diff --git a/package.json b/package.json index 631525c2..82cfbc16 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,8 @@ "@ember/render-modifiers": "^1.0.2", "ember-cli-babel": "^7.21.0", "ember-cli-htmlbars": "^5.2.0", - "ember-did-resize-modifier": "^1.0.0" + "ember-did-resize-modifier": "^1.0.0", + "ember-element-helper": "^0.3.1" }, "devDependencies": { "@ember/optional-features": "^1.3.0", @@ -91,8 +92,8 @@ "ember-template-lint": "^2.9.0", "ember-test-selectors": "^4.1.0", "ember-try": "^1.4.0", - "eslint": "^7.3.1", - "eslint-plugin-ember": "^8.9.0", + "eslint": "^7.4.0", + "eslint-plugin-ember": "^8.9.1", "eslint-plugin-node": "^11.1.0", "lerna-changelog": "^1.0.1", "loader.js": "^4.7.0", diff --git a/tests/dummy/app/components/widgets/widget-1/index.hbs b/tests/dummy/app/components/widgets/widget-1/index.hbs index 30844699..ef5774cd 100644 --- a/tests/dummy/app/components/widgets/widget-1/index.hbs +++ b/tests/dummy/app/components/widgets/widget-1/index.hbs @@ -4,6 +4,7 @@ square=(cq-aspect-ratio min=0.8 max=1.25) wide=(cq-aspect-ratio min=1.25) }} + @tagName="section" local-class="container" >
diff --git a/tests/dummy/app/components/widgets/widget-2/index.hbs b/tests/dummy/app/components/widgets/widget-2/index.hbs index c47b53c7..3d09c489 100644 --- a/tests/dummy/app/components/widgets/widget-2/index.hbs +++ b/tests/dummy/app/components/widgets/widget-2/index.hbs @@ -4,6 +4,7 @@ tall=(cq-height min=200 max=480) very-tall=(cq-height min=480) }} + @tagName="section" local-class="container" as |CQ| > diff --git a/tests/dummy/app/components/widgets/widget-3/index.hbs b/tests/dummy/app/components/widgets/widget-3/index.hbs index 1434b89e..141de826 100644 --- a/tests/dummy/app/components/widgets/widget-3/index.hbs +++ b/tests/dummy/app/components/widgets/widget-3/index.hbs @@ -1,4 +1,4 @@ -
+

Widget 3

@@ -17,4 +17,4 @@ @concert={{this.concertData}} />
- \ No newline at end of file + \ No newline at end of file diff --git a/tests/dummy/app/components/widgets/widget-4/index.hbs b/tests/dummy/app/components/widgets/widget-4/index.hbs index b168b5d0..d727606a 100644 --- a/tests/dummy/app/components/widgets/widget-4/index.hbs +++ b/tests/dummy/app/components/widgets/widget-4/index.hbs @@ -1,4 +1,4 @@ -
+

Widget 4

@@ -12,4 +12,4 @@ All memos
- \ No newline at end of file + \ No newline at end of file diff --git a/tests/dummy/app/components/widgets/widget-4/memo/index.hbs b/tests/dummy/app/components/widgets/widget-4/memo/index.hbs index 6a101e38..0326a8bf 100644 --- a/tests/dummy/app/components/widgets/widget-4/memo/index.hbs +++ b/tests/dummy/app/components/widgets/widget-4/memo/index.hbs @@ -4,6 +4,7 @@ large=(cq-width min=200) short=(cq-height max=200) }} + @tagName="article" local-class="container" as |CQ| > diff --git a/tests/dummy/app/components/widgets/widget-5/index.hbs b/tests/dummy/app/components/widgets/widget-5/index.hbs index f4f96e16..e583b186 100644 --- a/tests/dummy/app/components/widgets/widget-5/index.hbs +++ b/tests/dummy/app/components/widgets/widget-5/index.hbs @@ -3,6 +3,7 @@ large=(cq-width min=224) tall=(cq-height min=120) }} + @tagName="section" local-class="container" as |CQ| > diff --git a/tests/dummy/config/environment.js b/tests/dummy/config/environment.js index 9f239369..4b46d128 100644 --- a/tests/dummy/config/environment.js +++ b/tests/dummy/config/environment.js @@ -23,6 +23,12 @@ module.exports = function(environment) { } }; + ENV['ember-a11y-testing'] = { + componentOptions: { + turnAuditOff: true + } + } + if (environment === 'development') { // ENV.APP.LOG_RESOLVER = true; // ENV.APP.LOG_ACTIVE_GENERATION = true; diff --git a/tests/integration/components/container-query/tagName-test.js b/tests/integration/components/container-query/tagName-test.js new file mode 100644 index 00000000..389eb9fe --- /dev/null +++ b/tests/integration/components/container-query/tagName-test.js @@ -0,0 +1,222 @@ +import { set } from '@ember/object'; +import { render, settled } from '@ember/test-helpers'; +import setupContainerQueryTest from 'dummy/tests/helpers/container-query'; +import resizeContainer from 'dummy/tests/helpers/resize-container'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupRenderingTest } from 'ember-qunit'; +import { module, test } from 'qunit'; + +module('Integration | Component | container-query', function(hooks) { + setupRenderingTest(hooks); + setupContainerQueryTest(hooks); + + + module('When @tagName is undefined', function(hooks) { + hooks.beforeEach(async function() { + await render(hbs` +
+ +

{{CQ.features.small}}

+

{{CQ.features.medium}}

+

{{CQ.features.large}}

+ +

{{CQ.features.short}}

+

{{CQ.features.tall}}

+ +

{{CQ.features.ratio-type-A}}

+

{{CQ.features.ratio-type-B}}

+

{{CQ.features.ratio-type-C}}

+ +

{{CQ.dimensions.width}} x {{CQ.dimensions.height}}

+

{{CQ.dimensions.aspectRatio}}

+
+
+ `); + }); + + + test('The component has the
tag', async function(assert) { + assert.dom('[data-test-container-query]') + .hasTagName('div', 'Tag name is correct.'); + }); + + + test('The component continues to have the
tag when it is resized', async function(assert) { + await resizeContainer(500, 300); + + assert.dom('[data-test-container-query]') + .hasTagName('div', 'Tag name is correct.'); + + + await resizeContainer(800, 400); + + assert.dom('[data-test-container-query]') + .hasTagName('div', 'Tag name is correct.'); + + + await resizeContainer(1000, 600); + + assert.dom('[data-test-container-query]') + .hasTagName('div', 'Tag name is correct.'); + }); + }); + + + module('When @tagName is passed', function(hooks) { + hooks.beforeEach(async function() { + await render(hbs` +
+ +

{{CQ.features.small}}

+

{{CQ.features.medium}}

+

{{CQ.features.large}}

+ +

{{CQ.features.short}}

+

{{CQ.features.tall}}

+ +

{{CQ.features.ratio-type-A}}

+

{{CQ.features.ratio-type-B}}

+

{{CQ.features.ratio-type-C}}

+ +

{{CQ.dimensions.width}} x {{CQ.dimensions.height}}

+

{{CQ.dimensions.aspectRatio}}

+
+
+ `); + }); + + + test('The component has the correct tag', async function(assert) { + assert.dom('[data-test-container-query]') + .hasTagName('section', 'Tag name is correct.'); + }); + + + test('The component continues to have the correct tag when it is resized', async function(assert) { + await resizeContainer(500, 300); + + assert.dom('[data-test-container-query]') + .hasTagName('section', 'Tag name is correct.'); + + + await resizeContainer(800, 400); + + assert.dom('[data-test-container-query]') + .hasTagName('section', 'Tag name is correct.'); + + + await resizeContainer(1000, 600); + + assert.dom('[data-test-container-query]') + .hasTagName('section', 'Tag name is correct.'); + }); + }); + + + module('When @tagName is updated', function(hooks) { + hooks.beforeEach(async function() { + this.tagName = 'section'; + + await render(hbs` +
+ +

{{CQ.features.small}}

+

{{CQ.features.medium}}

+

{{CQ.features.large}}

+ +

{{CQ.features.short}}

+

{{CQ.features.tall}}

+ +

{{CQ.features.ratio-type-A}}

+

{{CQ.features.ratio-type-B}}

+

{{CQ.features.ratio-type-C}}

+ +

{{CQ.dimensions.width}} x {{CQ.dimensions.height}}

+

{{CQ.dimensions.aspectRatio}}

+
+
+ `); + + set(this, 'tagName', 'article'); + + await settled(); + }); + + + test('The component doesn\'t update the tag', async function(assert) { + assert.dom('[data-test-container-query]') + .hasTagName('section', 'Tag name is correct.') + .doesNotHaveTagName('article', 'Tag name did not change.'); + }); + + + test('The component continues to not update the tag when it is resized', async function(assert) { + await resizeContainer(500, 300); + + assert.dom('[data-test-container-query]') + .hasTagName('section', 'Tag name is correct.') + .doesNotHaveTagName('article', 'Tag name did not change.'); + + + await resizeContainer(800, 400); + + assert.dom('[data-test-container-query]') + .hasTagName('section', 'Tag name is correct.') + .doesNotHaveTagName('article', 'Tag name did not change.'); + + + await resizeContainer(1000, 600); + + assert.dom('[data-test-container-query]') + .hasTagName('section', 'Tag name is correct.') + .doesNotHaveTagName('article', 'Tag name did not change.'); + }); + }); +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 5d5c57fd..49f8ce0e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5889,7 +5889,7 @@ ember-cli-babel-plugin-helpers@^1.0.0, ember-cli-babel-plugin-helpers@^1.1.0: resolved "https://registry.yarnpkg.com/ember-cli-babel-plugin-helpers/-/ember-cli-babel-plugin-helpers-1.1.0.tgz#de3baedd093163b6c2461f95964888c1676325ac" integrity sha512-Zr4my8Xn+CzO0gIuFNXji0eTRml5AxZUTDQz/wsNJ5AJAtyFWCY4QtKdoELNNbiCVGt1lq5yLiwTm4scGKu6xA== -ember-cli-babel@7.21.0, ember-cli-babel@^7.21.0: +ember-cli-babel@7.21.0, ember-cli-babel@^7.17.2, ember-cli-babel@^7.21.0: version "7.21.0" resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.21.0.tgz#c79e888876aee87dfc3260aee7cb580b74264bbc" integrity sha512-jHVi9melAibo0DrAG3GAxid+29xEyjBoU53652B4qcu3Xp58feZGTH/JGXovH7TjvbeNn65zgNyoV3bk1onULw== @@ -6039,7 +6039,7 @@ ember-cli-htmlbars@^4.0.0: strip-bom "^4.0.0" walk-sync "^2.0.2" -ember-cli-htmlbars@^5.2.0: +ember-cli-htmlbars@^5.1.0, ember-cli-htmlbars@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-5.2.0.tgz#5ceccd0d18163dd810dea29f6fd777d0baa01e23" integrity sha512-EdjuUc7sq9ve6sgsG59qIzOj4svWjgYhO/QEhuV1UbOQ3ATeqNPiD++bFeAGjUhravw9HPhQPPoHnMlAikqLIw== @@ -6291,7 +6291,7 @@ ember-cli@~3.19.0: watch-detector "^1.0.0" yam "^1.0.0" -ember-compatibility-helpers@^1.1.2, ember-compatibility-helpers@^1.2.0: +ember-compatibility-helpers@^1.1.2, ember-compatibility-helpers@^1.2.0, ember-compatibility-helpers@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ember-compatibility-helpers/-/ember-compatibility-helpers-1.2.1.tgz#87c92c4303f990ff455c28ca39fb3ee11441aa16" integrity sha512-6wzYvnhg1ihQUT5yGqnLtleq3Nv5KNv79WhrEuNU9SwR4uIxCO+KpyC7r3d5VI0EM7/Nmv9Nd0yTkzmTMdVG1A== @@ -6339,6 +6339,15 @@ ember-disable-prototype-extensions@^1.1.3: resolved "https://registry.yarnpkg.com/ember-disable-prototype-extensions/-/ember-disable-prototype-extensions-1.1.3.tgz#1969135217654b5e278f9fe2d9d4e49b5720329e" integrity sha1-GWkTUhdlS14nj5/i2dTkm1cgMp4= +ember-element-helper@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/ember-element-helper/-/ember-element-helper-0.3.1.tgz#dd6017a64a9952058c16050c7a9eb0fff5ebfe90" + integrity sha512-U3tXkhiPsL1uIz2jCBZS4Lot0Le0wt7RM7TArYAR5OZRLGdCaLkRjQ0Xx5IlwWbBS0KOrfARevc1OLnX1AIgZQ== + dependencies: + ember-cli-babel "^7.17.2" + ember-cli-htmlbars "^5.1.0" + ember-compatibility-helpers "^1.2.1" + ember-export-application-global@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/ember-export-application-global/-/ember-export-application-global-2.0.1.tgz#b120a70e322ab208defc9e2daebe8d0dfc2dcd46" @@ -6783,10 +6792,10 @@ escodegen@^1.11.0: optionalDependencies: source-map "~0.6.1" -eslint-plugin-ember@^8.9.0: - version "8.9.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-ember/-/eslint-plugin-ember-8.9.0.tgz#010f720665b00847df151d5ae64f41e4671c825a" - integrity sha512-wSZuQM5AkDBc/FRL7jAIMVPQxzXSMUBgzGYzxhLNc4HOSBKVu6qqh2XZg7PTUmhst39VdYjDvep9QHmeRWdLkA== +eslint-plugin-ember@^8.9.1: + version "8.9.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-ember/-/eslint-plugin-ember-8.9.1.tgz#6d76440bd7bb1954ed61c7ebbb1a1cd8c8e1afa6" + integrity sha512-A7TFksLfLLoQEWOHCvJX6SvxxL99yDEPwDZIh28TOgwsPsXpVYJNk22UT5ZrufUyVtdnOH4IsF3jIkRIIE91IA== dependencies: "@ember-data/rfc395-data" "^0.0.4" ember-rfc176-data "^0.3.13" @@ -6846,10 +6855,10 @@ eslint-visitor-keys@^1.2.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz#74415ac884874495f78ec2a97349525344c981fa" integrity sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ== -eslint@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.3.1.tgz#76392bd7e44468d046149ba128d1566c59acbe19" - integrity sha512-cQC/xj9bhWUcyi/RuMbRtC3I0eW8MH0jhRELSvpKYkWep3C6YZ2OkvcvJVUeO6gcunABmzptbXBuDoXsjHmfTA== +eslint@^7.4.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.4.0.tgz#4e35a2697e6c1972f9d6ef2b690ad319f80f206f" + integrity sha512-gU+lxhlPHu45H3JkEGgYhWhkR9wLHHEXC9FbWFnTlEkbKyZKWgWRLgf61E8zWmBuI6g5xKBph9ltg3NtZMVF8g== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.10.0"