diff --git a/package-lock.json b/package-lock.json index 001f5550696..1ad0f97ce54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47015,7 +47015,7 @@ }, "packages/calcite-components": { "name": "@esri/calcite-components", - "version": "2.3.0", + "version": "2.4.0-next.0", "license": "SEE LICENSE.md", "dependencies": { "@floating-ui/dom": "1.5.4", @@ -47033,7 +47033,7 @@ "devDependencies": { "@esri/calcite-design-tokens": "^2.1.2-next.0", "@esri/calcite-ui-icons": "3.25.6", - "@esri/eslint-plugin-calcite-components": "^1.0.1-next.2", + "@esri/eslint-plugin-calcite-components": "^1.1.0-next.0", "@stencil-community/eslint-plugin": "0.7.1", "@stencil-community/postcss": "2.2.0", "@stencil/angular-output-target": "0.8.3", @@ -47066,10 +47066,10 @@ }, "packages/calcite-components-angular/projects/component-library": { "name": "@esri/calcite-components-angular", - "version": "2.3.0", + "version": "2.4.0-next.0", "license": "SEE LICENSE.md", "dependencies": { - "@esri/calcite-components": "^2.3.0", + "@esri/calcite-components": "^2.4.0-next.0", "tslib": "2.6.2" }, "peerDependencies": { @@ -47079,10 +47079,10 @@ }, "packages/calcite-components-react": { "name": "@esri/calcite-components-react", - "version": "2.3.0", + "version": "2.4.0-next.0", "license": "SEE LICENSE.md", "dependencies": { - "@esri/calcite-components": "^2.3.0" + "@esri/calcite-components": "^2.4.0-next.0" }, "peerDependencies": { "react": ">=16.7", @@ -47099,7 +47099,7 @@ }, "packages/eslint-plugin-calcite-components": { "name": "@esri/eslint-plugin-calcite-components", - "version": "1.0.1-next.2", + "version": "1.1.0-next.0", "license": "SEE LICENSE.md", "dependencies": { "stencil-eslint-core": "0.4.1" diff --git a/packages/calcite-components-angular/projects/component-library/CHANGELOG.md b/packages/calcite-components-angular/projects/component-library/CHANGELOG.md index b9097612433..8b70f13f332 100644 --- a/packages/calcite-components-angular/projects/component-library/CHANGELOG.md +++ b/packages/calcite-components-angular/projects/component-library/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.4.0-next.0](https://github.com/Esri/calcite-design-system/compare/@esri/calcite-components-angular@2.3.1-next.0...@esri/calcite-components-angular@2.4.0-next.0) (2024-01-26) + +**Note:** Version bump only for package @esri/calcite-components-angular + +## [2.3.1-next.0](https://github.com/Esri/calcite-design-system/compare/@esri/calcite-components-angular@2.3.0...@esri/calcite-components-angular@2.3.1-next.0) (2024-01-26) + +**Note:** Version bump only for package @esri/calcite-components-angular + ## [2.3.0](https://github.com/Esri/calcite-design-system/compare/@esri/calcite-components-angular@2.2.0...@esri/calcite-components-angular@2.3.0) (2024-01-24) ### Miscellaneous Chores diff --git a/packages/calcite-components-angular/projects/component-library/package.json b/packages/calcite-components-angular/projects/component-library/package.json index 9538e9909da..324692ce634 100644 --- a/packages/calcite-components-angular/projects/component-library/package.json +++ b/packages/calcite-components-angular/projects/component-library/package.json @@ -1,6 +1,6 @@ { "name": "@esri/calcite-components-angular", - "version": "2.3.0", + "version": "2.4.0-next.0", "sideEffects": false, "homepage": "https://developers.arcgis.com/calcite-design-system/", "description": "A set of Angular components that wrap Esri's Calcite Components.", @@ -20,7 +20,7 @@ "@angular/core": ">=16.0.0" }, "dependencies": { - "@esri/calcite-components": "^2.3.0", + "@esri/calcite-components": "^2.4.0-next.0", "tslib": "2.6.2" }, "lerna": { diff --git a/packages/calcite-components-react/CHANGELOG.md b/packages/calcite-components-react/CHANGELOG.md index 79ec329ecc4..b9412298d52 100644 --- a/packages/calcite-components-react/CHANGELOG.md +++ b/packages/calcite-components-react/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.4.0-next.0](https://github.com/Esri/calcite-design-system/compare/@esri/calcite-components-react@2.3.1-next.0...@esri/calcite-components-react@2.4.0-next.0) (2024-01-26) + +**Note:** Version bump only for package @esri/calcite-components-react + +## [2.3.1-next.0](https://github.com/Esri/calcite-design-system/compare/@esri/calcite-components-react@2.3.0...@esri/calcite-components-react@2.3.1-next.0) (2024-01-26) + +**Note:** Version bump only for package @esri/calcite-components-react + ## [2.3.0](https://github.com/Esri/calcite-design-system/compare/@esri/calcite-components-react@2.2.0...@esri/calcite-components-react@2.3.0) (2024-01-24) ### Miscellaneous Chores diff --git a/packages/calcite-components-react/package.json b/packages/calcite-components-react/package.json index a3822f3b05b..6931d48417d 100644 --- a/packages/calcite-components-react/package.json +++ b/packages/calcite-components-react/package.json @@ -1,7 +1,7 @@ { "name": "@esri/calcite-components-react", "sideEffects": false, - "version": "2.3.0", + "version": "2.4.0-next.0", "homepage": "https://developers.arcgis.com/calcite-design-system/", "description": "A set of React components that wrap calcite components", "license": "SEE LICENSE.md", @@ -23,7 +23,7 @@ "dist/" ], "dependencies": { - "@esri/calcite-components": "^2.3.0" + "@esri/calcite-components": "^2.4.0-next.0" }, "peerDependencies": { "react": ">=16.7", diff --git a/packages/calcite-components/.eslintrc.cjs b/packages/calcite-components/.eslintrc.cjs index 8d7ea161412..ecdf79c1b67 100644 --- a/packages/calcite-components/.eslintrc.cjs +++ b/packages/calcite-components/.eslintrc.cjs @@ -157,4 +157,10 @@ module.exports = { ignorePrivate: true, }, }, + overrides: [{ + files: ["**/*.e2e.ts", "src/tests/**/*"], + rules: { + "@esri/calcite-components/no-dynamic-createelement": "off", + } + }] }; diff --git a/packages/calcite-components/CHANGELOG.md b/packages/calcite-components/CHANGELOG.md index c1728338c96..3b3f43a0f15 100644 --- a/packages/calcite-components/CHANGELOG.md +++ b/packages/calcite-components/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.4.0-next.0](https://github.com/Esri/calcite-design-system/compare/@esri/calcite-components@2.3.1-next.0...@esri/calcite-components@2.4.0-next.0) (2024-01-26) + +### Features + +- add `no-dynamic-createelement` rule ([#8656](https://github.com/Esri/calcite-design-system/issues/8656)) ([c7e9444](https://github.com/Esri/calcite-design-system/commit/c7e94441f8cc263935e60a6c920dd9673af9b8c0)), closes [#8651](https://github.com/Esri/calcite-design-system/issues/8651) + +### Bug Fixes + +- **combobox:** ensure supporting components are auto-defined ([#8657](https://github.com/Esri/calcite-design-system/issues/8657)) ([e6d792b](https://github.com/Esri/calcite-design-system/commit/e6d792b1a78a3a21a54f04fda4c4795d336deba8)), closes [#8495](https://github.com/Esri/calcite-design-system/issues/8495) + +## [2.3.1-next.0](https://github.com/Esri/calcite-design-system/compare/@esri/calcite-components@2.3.0...@esri/calcite-components@2.3.1-next.0) (2024-01-26) + +### Bug Fixes + +- **tile-select:** ensure supporting components are auto-defined ([#8648](https://github.com/Esri/calcite-design-system/issues/8648)) ([2c27f40](https://github.com/Esri/calcite-design-system/commit/2c27f409fedfd12bb06b3b4f0e0355816bb50e9a)), closes [#8495](https://github.com/Esri/calcite-design-system/issues/8495) + ## [2.3.0](https://github.com/Esri/calcite-design-system/compare/@esri/calcite-components@2.2.0...@esri/calcite-components@2.3.0) (2024-01-24) ### Features diff --git a/packages/calcite-components/package.json b/packages/calcite-components/package.json index ea35a15899a..51b8dcdcc49 100644 --- a/packages/calcite-components/package.json +++ b/packages/calcite-components/package.json @@ -1,6 +1,6 @@ { "name": "@esri/calcite-components", - "version": "2.3.0", + "version": "2.4.0-next.0", "homepage": "https://developers.arcgis.com/calcite-design-system/", "description": "Web Components for Esri's Calcite Design System.", "main": "dist/index.cjs.js", @@ -77,7 +77,7 @@ "devDependencies": { "@esri/calcite-design-tokens": "^2.1.2-next.0", "@esri/calcite-ui-icons": "3.25.6", - "@esri/eslint-plugin-calcite-components": "^1.0.1-next.2", + "@esri/eslint-plugin-calcite-components": "^1.1.0-next.0", "@stencil-community/eslint-plugin": "0.7.1", "@stencil-community/postcss": "2.2.0", "@stencil/angular-output-target": "0.8.3", diff --git a/packages/calcite-components/src/components/combobox/combobox.tsx b/packages/calcite-components/src/components/combobox/combobox.tsx index 1c5af4c1871..86a74ece1dc 100644 --- a/packages/calcite-components/src/components/combobox/combobox.tsx +++ b/packages/calcite-components/src/components/combobox/combobox.tsx @@ -1221,7 +1221,7 @@ export class Combobox if (!this.isMulti()) { this.toggleSelection(this.selectedItems[this.selectedItems.length - 1], false); } - const item = document.createElement(ComboboxItem) as HTMLCalciteComboboxItemElement; + const item = document.createElement("calcite-combobox-item"); item.value = value; item.textLabel = value; item.selected = true; diff --git a/packages/calcite-components/src/components/tile-select/tile-select.tsx b/packages/calcite-components/src/components/tile-select/tile-select.tsx index 9bc5dcb3501..a751c5ed6f1 100644 --- a/packages/calcite-components/src/components/tile-select/tile-select.tsx +++ b/packages/calcite-components/src/components/tile-select/tile-select.tsx @@ -275,9 +275,11 @@ export class TileSelect implements InteractiveComponent, LoadableComponent { // -------------------------------------------------------------------------- private renderInput(): void { - this.input = document.createElement( - this.type === "radio" ? "calcite-radio-button" : "calcite-checkbox", - ); + this.input = + this.type === "radio" + ? /* we need to call createElement(x) separately to ensure supporting components are properly bundled */ + document.createElement("calcite-radio-button") + : document.createElement("calcite-checkbox"); this.input.checked = this.checked; this.input.disabled = this.disabled; this.input.hidden = this.el.hidden; diff --git a/packages/eslint-plugin-calcite-components/CHANGELOG.md b/packages/eslint-plugin-calcite-components/CHANGELOG.md index ae2dac653b3..5e8e5a0cb47 100644 --- a/packages/eslint-plugin-calcite-components/CHANGELOG.md +++ b/packages/eslint-plugin-calcite-components/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.0-next.0](https://github.com/Esri/calcite-design-system/compare/@esri/eslint-plugin-calcite-components@1.0.1-next.2...@esri/eslint-plugin-calcite-components@1.1.0-next.0) (2024-01-26) + +### Features + +- add `no-dynamic-createelement` rule ([#8656](https://github.com/Esri/calcite-design-system/issues/8656)) ([c7e9444](https://github.com/Esri/calcite-design-system/commit/c7e94441f8cc263935e60a6c920dd9673af9b8c0)), closes [#8651](https://github.com/Esri/calcite-design-system/issues/8651) + ## [1.0.1-next.2](https://github.com/Esri/calcite-design-system/compare/@esri/eslint-plugin-calcite-components@1.0.1-next.1...@esri/eslint-plugin-calcite-components@1.0.1-next.2) (2024-01-05) **Note:** Version bump only for package @esri/eslint-plugin-calcite-components diff --git a/packages/eslint-plugin-calcite-components/docs/no-dynamic-createelement.md b/packages/eslint-plugin-calcite-components/docs/no-dynamic-createelement.md new file mode 100644 index 00000000000..0da5b24a62d --- /dev/null +++ b/packages/eslint-plugin-calcite-components/docs/no-dynamic-createelement.md @@ -0,0 +1,13 @@ +# no-dynamic-createelement + +Helps prevent usage of dynamic element tags used with `createElement`. This ensures the component is properly bundled and auto-defined in Stencil's `components` output target. + +## Config + +No config is needed + +## Usage + +```json +{ "@esri/calcite-components/no-dynamic-createelement": "error" } +``` diff --git a/packages/eslint-plugin-calcite-components/package.json b/packages/eslint-plugin-calcite-components/package.json index 02faf4b7385..5788ef19347 100644 --- a/packages/eslint-plugin-calcite-components/package.json +++ b/packages/eslint-plugin-calcite-components/package.json @@ -1,6 +1,6 @@ { "name": "@esri/eslint-plugin-calcite-components", - "version": "1.0.1-next.2", + "version": "1.1.0-next.0", "description": "ESLint rules for @esri/calcite-components", "main": "dist/index.js", "files": [ diff --git a/packages/eslint-plugin-calcite-components/src/configs/base.ts b/packages/eslint-plugin-calcite-components/src/configs/base.ts index 2cf871f9739..89ff13683e7 100644 --- a/packages/eslint-plugin-calcite-components/src/configs/base.ts +++ b/packages/eslint-plugin-calcite-components/src/configs/base.ts @@ -18,6 +18,7 @@ export default { rules: { "@esri/calcite-components/ban-props-on-host": 2, "@esri/calcite-components/enforce-ref-last-prop": 2, + "@esri/calcite-components/no-dynamic-createelement": 2, "@esri/calcite-components/require-event-emitter-type": 2, "@esri/calcite-components/strict-boolean-attributes": 2, }, diff --git a/packages/eslint-plugin-calcite-components/src/rules/index.ts b/packages/eslint-plugin-calcite-components/src/rules/index.ts index 239a3ce77e0..4499f42a295 100644 --- a/packages/eslint-plugin-calcite-components/src/rules/index.ts +++ b/packages/eslint-plugin-calcite-components/src/rules/index.ts @@ -1,6 +1,7 @@ import banEvents from "./ban-events"; import banPropsOnHost from "./ban-props-on-host"; import enforceRefLastProp from "./enforce-ref-last-prop"; +import noDynamicCreateelement from "./no-dynamic-createelement"; import requireEventEmitterType from "./require-event-emitter-type"; import strictBooleanAttributes from "./strict-boolean-attributes"; @@ -8,6 +9,7 @@ export default { "ban-events": banEvents, "ban-props-on-host": banPropsOnHost, "enforce-ref-last-prop": enforceRefLastProp, + "no-dynamic-createelement": noDynamicCreateelement, "require-event-emitter-type": requireEventEmitterType, "strict-boolean-attributes": strictBooleanAttributes, }; diff --git a/packages/eslint-plugin-calcite-components/src/rules/no-dynamic-createelement.ts b/packages/eslint-plugin-calcite-components/src/rules/no-dynamic-createelement.ts new file mode 100644 index 00000000000..83217ccf7c4 --- /dev/null +++ b/packages/eslint-plugin-calcite-components/src/rules/no-dynamic-createelement.ts @@ -0,0 +1,46 @@ +import { Rule } from "eslint"; + +function isCreateElement(node) { + return ( + node?.callee?.type === "MemberExpression" && + node?.callee?.object?.name === "document" && + node?.callee?.property?.name === "createElement" && + node.arguments.length >= 1 + ); +} + +function isStaticValue(arg) { + return arg.type === "Literal" || (arg.type === "TemplateLiteral" && arg.expressions.length === 0); +} + +const rule: Rule.RuleModule = { + meta: { + docs: { + description: + "This ensures supporting components created with `document.createElement()` are auto-defined in Stencil's `components` output target.", + recommended: true, + }, + fixable: "code", + schema: [], + type: "problem", + }, + + create(context) { + return { + CallExpression(node) { + if (!node.arguments[0] || isStaticValue(node.arguments[0])) { + return; + } + + if (isCreateElement(node)) { + return context.report({ + node, + message: "Calls to document.createElement() should use string literals", + }); + } + }, + }; + }, +}; + +export default rule; diff --git a/packages/eslint-plugin-calcite-components/tests/lib/rules/no-dynamic-createelement/no-dynamic-createelement.good.tsx b/packages/eslint-plugin-calcite-components/tests/lib/rules/no-dynamic-createelement/no-dynamic-createelement.good.tsx new file mode 100644 index 00000000000..9291ab12dc9 --- /dev/null +++ b/packages/eslint-plugin-calcite-components/tests/lib/rules/no-dynamic-createelement/no-dynamic-createelement.good.tsx @@ -0,0 +1,19 @@ +// @ts-nocheck +@Component({ tag: "sample-tag" }) +export class SampleTag { + @Prop() + type: "one" | "two" = "one"; + + connectedCallback() { + const child = + this.type === "one" + ? document.createElement("my-component-1") + : document.createElement("my-component-2"); + this.el.append(child); + this.internalEl = child; + } + + disconnectedCallback() { + this.internalEl.remove(); + } +} diff --git a/packages/eslint-plugin-calcite-components/tests/lib/rules/no-dynamic-createelement/no-dynamic-createelement.spec.ts b/packages/eslint-plugin-calcite-components/tests/lib/rules/no-dynamic-createelement/no-dynamic-createelement.spec.ts new file mode 100644 index 00000000000..197aabd331e --- /dev/null +++ b/packages/eslint-plugin-calcite-components/tests/lib/rules/no-dynamic-createelement/no-dynamic-createelement.spec.ts @@ -0,0 +1,29 @@ +import rule from "../../../../src/rules/no-dynamic-createelement"; +import { ruleTester } from "stencil-eslint-core"; +import * as path from "path"; +import * as fs from "fs"; + +const projectPath = path.resolve(__dirname, "../../../tsconfig.json"); + +describe("no-dynamic-createelement rule", () => { + const files = { + good: path.resolve(__dirname, "no-dynamic-createelement.good.tsx"), + wrong: path.resolve(__dirname, "no-dynamic-createelement.wrong.tsx"), + }; + ruleTester(projectPath).run("no-dynamic-createelement", rule, { + valid: [ + { + code: fs.readFileSync(files.good, "utf8"), + filename: files.good, + }, + ], + + invalid: [ + { + code: fs.readFileSync(files.wrong, "utf8"), + filename: files.wrong, + errors: 1, + }, + ], + }); +}); diff --git a/packages/eslint-plugin-calcite-components/tests/lib/rules/no-dynamic-createelement/no-dynamic-createelement.wrong.tsx b/packages/eslint-plugin-calcite-components/tests/lib/rules/no-dynamic-createelement/no-dynamic-createelement.wrong.tsx new file mode 100644 index 00000000000..459da27cf0e --- /dev/null +++ b/packages/eslint-plugin-calcite-components/tests/lib/rules/no-dynamic-createelement/no-dynamic-createelement.wrong.tsx @@ -0,0 +1,16 @@ +// @ts-nocheck +@Component({ tag: "sample-tag" }) +export class SampleTag { + @Prop() + type: "one" | "two" = "one"; + + connectedCallback() { + const child = document.createElement(this.type === "one" ? "my-component-1" : "my-component-2"); + this.el.append(child); + this.internalEl = child; + } + + disconnectedCallback() { + this.internalEl.remove(); + } +}