diff --git a/addon/components/api/x-class/index.js b/addon/components/api/x-class/index.js index 744eee3e7..e0d390c80 100644 --- a/addon/components/api/x-class/index.js +++ b/addon/components/api/x-class/index.js @@ -1,7 +1,7 @@ import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { or } from '@ember/object/computed'; -import { capitalize } from '@ember/string'; +import { capitalize } from '../../../utils/string'; import { memberFilter } from '../../../utils/computed'; import { addonDocsConfig } from 'ember-cli-addon-docs/-private/config'; diff --git a/addon/components/api/x-component/index.js b/addon/components/api/x-component/index.js index 734c19256..4c15e8553 100644 --- a/addon/components/api/x-component/index.js +++ b/addon/components/api/x-component/index.js @@ -2,7 +2,7 @@ import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { alias, or } from '@ember/object/computed'; -import { capitalize } from '@ember/string'; +import { capitalize } from '../../../utils/string'; import { memberFilter } from '../../../utils/computed'; export default class XComponent extends Component { diff --git a/addon/components/docs-header/index.js b/addon/components/docs-header/index.js index d80c30c13..d36fffc85 100644 --- a/addon/components/docs-header/index.js +++ b/addon/components/docs-header/index.js @@ -1,6 +1,6 @@ import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; -import { classify } from '@ember/string'; +import { classify } from '../../utils/string'; import { addonPrefix } from 'ember-cli-addon-docs/utils/computed'; import { inject as service } from '@ember/service'; import { reads } from '@ember/object/computed'; diff --git a/addon/components/docs-hero/index.js b/addon/components/docs-hero/index.js index c99511d3a..fdb4dc862 100644 --- a/addon/components/docs-hero/index.js +++ b/addon/components/docs-hero/index.js @@ -3,7 +3,7 @@ import { addonPrefix, unprefixedAddonName, } from 'ember-cli-addon-docs/utils/computed'; -import { classify } from '@ember/string'; +import { classify } from '../../utils/string'; import { addonDocsConfig } from 'ember-cli-addon-docs/-private/config'; /** diff --git a/addon/components/docs-viewer/x-nav/index.js b/addon/components/docs-viewer/x-nav/index.js index 68a0db1ce..93143e3c2 100644 --- a/addon/components/docs-viewer/x-nav/index.js +++ b/addon/components/docs-viewer/x-nav/index.js @@ -2,7 +2,7 @@ import { inject as service } from '@ember/service'; import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { localCopy } from 'tracked-toolbox'; -import { classify } from '@ember/string'; +import { classify } from '../../../utils/string'; import { addonLogo } from 'ember-cli-addon-docs/utils/computed'; import { addonDocsConfig } from 'ember-cli-addon-docs/-private/config'; diff --git a/addon/helpers/capitalize.js b/addon/helpers/capitalize.js index 8c30a5874..50e56e39b 100644 --- a/addon/helpers/capitalize.js +++ b/addon/helpers/capitalize.js @@ -1,5 +1,5 @@ import { helper } from '@ember/component/helper'; -import { capitalize } from '@ember/string'; +import { capitalize } from '../utils/string'; export default helper(function capitalizeHelper(positional) { return capitalize(positional[0]); diff --git a/addon/models/component.js b/addon/models/component.js index 4aa6f4a79..c8008c589 100644 --- a/addon/models/component.js +++ b/addon/models/component.js @@ -1,6 +1,6 @@ import { attr } from '@ember-data/model'; import { filterBy, or } from '@ember/object/computed'; -import { dasherize } from '@ember/string'; +import { dasherize } from '../utils/string'; import Class from './class'; import { memberUnion, hasMemberType } from '../utils/computed'; diff --git a/addon/utils/computed.js b/addon/utils/computed.js index 5815e8b14..16ad61c0e 100644 --- a/addon/utils/computed.js +++ b/addon/utils/computed.js @@ -1,5 +1,5 @@ import { computed, get } from '@ember/object'; -import { capitalize } from '@ember/string'; +import { capitalize } from './string'; /** @function initialize diff --git a/addon/utils/string.js b/addon/utils/string.js new file mode 100644 index 000000000..62c6a8ff6 --- /dev/null +++ b/addon/utils/string.js @@ -0,0 +1,49 @@ +/** + * Capitalizes the first letter of a string + * @param {string} str - The string to capitalize + * @returns {string} The capitalized string + */ +export function capitalize(str) { + if (!str || typeof str !== 'string') { + return str; + } + return str.charAt(0).toUpperCase() + str.slice(1); +} + +/** + * Converts a string to kebab-case (dasherized) + * @param {string} str - The string to dasherize + * @returns {string} The dasherized string + */ +export function dasherize(str) { + if (!str || typeof str !== 'string') { + return str; + } + return str + .replace(/([a-z\d])([A-Z])/g, '$1-$2') // Add dash between camelCase + .replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1-$2') // Handle consecutive capitals + .replace(/[ _]/g, '-') // Replace spaces and underscores with dashes + .toLowerCase(); +} + +/** + * Converts a string to PascalCase (classified) + * @param {string} str - The string to classify + * @returns {string} The classified string + */ +export function classify(str) { + if (!str || typeof str !== 'string') { + return str; + } + + // If the string contains separators (spaces, dashes, underscores), split and classify + if (/[\s_-]/.test(str)) { + return str + .split(/[\s_-]+/) + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(''); + } + + // Otherwise, just capitalize the first letter (preserve camelCase like innerHTML -> InnerHTML) + return str.charAt(0).toUpperCase() + str.slice(1); +} diff --git a/package.json b/package.json index 42c838ddd..f04e4c9b9 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "ember-composable-helpers": "^5.0.0", "ember-concurrency": "^5.1.0", "ember-keyboard": "^9.0.4", - "ember-modal-dialog": "^4.1.5", + "ember-modal-dialog": "^5.0.0", "ember-router-generator": "^2.0.0", "ember-set-helper": "^2.0.1", "ember-svg-jar": "^2.7.1", @@ -107,7 +107,6 @@ "@ember-data/serializer": "^5.8.0", "@ember-data/store": "^5.8.0", "@ember/optional-features": "^2.3.0", - "@ember/string": "^3.1.1", "@ember/test-helpers": "^3.3.1", "@ember/test-waiters": "^3.1.0", "@embroider/test-setup": "^3.0.3", @@ -164,7 +163,6 @@ "@ember-data/model": ">= 3.0.0", "@ember-data/serializer": ">= 3.0.0", "@ember-data/store": ">= 3.0.0", - "@ember/string": "^3.1.1 || ^4.0.0", "@ember/test-waiters": "^3.1.0 || ^4.0.0", "ember-data": ">= 3.0.0", "ember-inflector": ">= 4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index def193901..654ec64e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -123,8 +123,8 @@ importers: specifier: ^9.0.4 version: 9.0.4(@babel/core@7.28.5)(@ember/test-helpers@3.3.1(@babel/core@7.28.5)(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0))(webpack@5.103.0)) ember-modal-dialog: - specifier: ^4.1.5 - version: 4.1.5(@ember/string@3.1.1(@babel/core@7.28.5))(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0))(ember-tether@3.1.0(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0))(webpack@5.103.0)) + specifier: ^5.0.0 + version: 5.0.0(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0))(ember-tether@3.1.0(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0))(webpack@5.103.0))(liquid-fire@0.37.1(@babel/core@7.28.5)(velocity-animate@1.5.2))(velocity-animate@1.5.2) ember-router-generator: specifier: ^2.0.0 version: 2.0.0 @@ -261,9 +261,6 @@ importers: '@ember/optional-features': specifier: ^2.3.0 version: 2.3.0 - '@ember/string': - specifier: ^3.1.1 - version: 3.1.1(@babel/core@7.28.5) '@ember/test-helpers': specifier: ^3.3.1 version: 3.3.1(@babel/core@7.28.5)(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0))(webpack@5.103.0) @@ -417,9 +414,6 @@ importers: test-apps/new-addon: dependencies: - '@ember/string': - specifier: ^3.1.1 - version: 3.1.1(@babel/core@7.28.5) ember-auto-import: specifier: ^2.12.0 version: 2.12.0(webpack@5.103.0) @@ -1267,10 +1261,6 @@ packages: '@glint/template': optional: true - '@ember/string@3.1.1': - resolution: {integrity: sha512-UbXJ+k3QOrYN4SRPHgXCqYIJ+yWWUg1+vr0H4DhdQPTy8LJfyqwZ2tc5uqpSSnEXE+/1KopHBE5J8GDagAg5cg==} - engines: {node: 12.* || 14.* || >= 16} - '@ember/test-helpers@3.3.1': resolution: {integrity: sha512-h4uFBy4pquBtHsHI+tx9S0wtMmn1L+8dkXiDiyoqG1+3e0Awk6GBujiFM9s4ANq6wC8uIhC3wEFyts10h2OAoQ==} engines: {node: 16.* || >= 18} @@ -1321,8 +1311,8 @@ packages: '@embroider/webpack': optional: true - '@embroider/util@1.13.4': - resolution: {integrity: sha512-TqA0SNQarSJUdYGv+39MBCHkiuxhr2u0iKJP/JnDmQkCiVhvuFWy3P3n5sI26fVrVwG3DJLfxE2XVnB37udFOA==} + '@embroider/util@1.13.5': + resolution: {integrity: sha512-rHhGUzAQ5iOr5Swvk7yaarVe5SJtcjK2t/C8ts9agWfhTq4DVfy8+axF0KOf1jALRiJao3l9ALRGd6letKw2ZQ==} engines: {node: 12.* || 14.* || >= 16} peerDependencies: '@glint/environment-ember-loose': ^1.0.0 @@ -3944,14 +3934,15 @@ packages: resolution: {integrity: sha512-CYR+U/wRxLbrfYN3dh+0Tb6mFaxJKfdyz+wNql6cqTrA0BBi9k6J3AaKXj273TqvEpyyXegQFFkZEiuZdYtgJw==} engines: {node: 6.* || 8.* || >= 10.*} - ember-modal-dialog@4.1.5: - resolution: {integrity: sha512-VioV8l0B5N7kvqmV/dupwWQBdAHMDNjmONzo/mW1kHkF4RXyGzgbUqzNHc5cTF9yuEeXKOLf0CBwWEMMM8A9AA==} + ember-modal-dialog@5.0.0: + resolution: {integrity: sha512-+POBUIIeRjgsJBO9VC11Qv9OQt6AnW7BPX/wHB/wmICMIvMTOWPPfjSs49YOuHjMCS/ovCBHCixmPuiIlwUrFQ==} engines: {node: 18.* || >= 20} peerDependencies: - '@ember/string': ^3.0.0 || ^4.0.0 ember-tether: ^3.0.0 - liquid-tether: ^2.0.7 - liquid-wormhole: ^3.0.1 + liquid-fire: ^0.37.1 + liquid-tether: ^3.0.0 + liquid-wormhole: ^6.0.0 + velocity-animate: ^1.5.2 peerDependenciesMeta: ember-tether: optional: true @@ -5643,6 +5634,11 @@ packages: linkify-it@4.0.1: resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==} + liquid-fire@0.37.1: + resolution: {integrity: sha512-ULu4OlcqSB1iJp+/NPPqJCi8XgiMDHuhjKLsazsQTb4xB15FI2HFV/DCfzvFxfUtoaeKlnmxsrUATBmPww0Kgg==} + peerDependencies: + velocity-animate: ^1.5.2 + livereload-js@3.4.1: resolution: {integrity: sha512-5MP0uUeVCec89ZbNOT/i97Mc+q3SxXmiUGhRFOTmhrGPn//uWVQdCvcLJDy64MSBR5MidFdOR7B9viumoavy6g==} @@ -8256,6 +8252,9 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + velocity-animate@1.5.2: + resolution: {integrity: sha512-m6EXlCAMetKztO1ppBhGU1/1MR3IiEevO6ESq6rcrSQ3Q77xYSW13jkfXW88o4xMrkXJhy/U7j4wFR/twMB0Eg==} + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -9532,13 +9531,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@ember/string@3.1.1(@babel/core@7.28.5)': - dependencies: - ember-cli-babel: 8.2.0(@babel/core@7.28.5) - transitivePeerDependencies: - - '@babel/core' - - supports-color - '@ember/test-helpers@3.3.1(@babel/core@7.28.5)(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0))(webpack@5.103.0)': dependencies: '@ember/test-waiters': 3.1.0(@babel/core@7.28.5) @@ -9635,7 +9627,7 @@ snapshots: lodash: 4.17.21 resolve: 1.22.11 - '@embroider/util@1.13.4(@babel/core@7.28.5)(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0))': + '@embroider/util@1.13.5(@babel/core@7.28.5)(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0))': dependencies: '@embroider/macros': 1.19.5(@babel/core@7.28.5) broccoli-funnel: 3.0.8 @@ -13258,16 +13250,17 @@ snapshots: - '@babel/core' - supports-color - ember-modal-dialog@4.1.5(@ember/string@3.1.1(@babel/core@7.28.5))(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0))(ember-tether@3.1.0(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0))(webpack@5.103.0)): + ember-modal-dialog@5.0.0(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0))(ember-tether@3.1.0(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0))(webpack@5.103.0))(liquid-fire@0.37.1(@babel/core@7.28.5)(velocity-animate@1.5.2))(velocity-animate@1.5.2): dependencies: '@babel/core': 7.28.5 - '@ember/string': 3.1.1(@babel/core@7.28.5) '@embroider/macros': 1.19.5(@babel/core@7.28.5) - '@embroider/util': 1.13.4(@babel/core@7.28.5)(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0)) + '@embroider/util': 1.13.5(@babel/core@7.28.5)(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0)) ember-cli-babel: 8.2.0(@babel/core@7.28.5) ember-cli-htmlbars: 6.3.0 ember-cli-version-checker: 5.1.2 ember-wormhole: 0.6.1(@babel/core@7.28.5) + liquid-fire: 0.37.1(@babel/core@7.28.5)(velocity-animate@1.5.2) + velocity-animate: 1.5.2 optionalDependencies: ember-tether: 3.1.0(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.28.5))(rsvp@4.8.5)(webpack@5.103.0))(webpack@5.103.0) transitivePeerDependencies: @@ -15483,6 +15476,17 @@ snapshots: dependencies: uc.micro: 1.0.6 + liquid-fire@0.37.1(@babel/core@7.28.5)(velocity-animate@1.5.2): + dependencies: + '@embroider/addon-shim': 1.10.2 + '@embroider/macros': 1.19.5(@babel/core@7.28.5) + ember-modifier: 4.2.2(@babel/core@7.28.5) + velocity-animate: 1.5.2 + transitivePeerDependencies: + - '@babel/core' + - '@glint/template' + - supports-color + livereload-js@3.4.1: {} loader-runner@4.3.1: {} @@ -18498,6 +18502,8 @@ snapshots: vary@1.1.2: {} + velocity-animate@1.5.2: {} + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 diff --git a/test-apps/new-addon/package.json b/test-apps/new-addon/package.json index 5f948386e..6ce31d23c 100644 --- a/test-apps/new-addon/package.json +++ b/test-apps/new-addon/package.json @@ -20,7 +20,6 @@ "test:all": "ember try:each" }, "dependencies": { - "@ember/string": "^3.1.1", "ember-cli-babel": "*", "ember-auto-import": "*" }, diff --git a/tests/integration/helpers/capitalize-test.js b/tests/integration/helpers/capitalize-test.js index 9d464d1ca..9814d3a59 100644 --- a/tests/integration/helpers/capitalize-test.js +++ b/tests/integration/helpers/capitalize-test.js @@ -2,7 +2,7 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'dummy/tests/helpers'; import { render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; -import { capitalize } from '@ember/string'; +import { capitalize } from 'ember-cli-addon-docs/utils/string'; module('Integration | Helper | capitalize', function (hooks) { setupRenderingTest(hooks); diff --git a/tests/unit/utils/string-test.js b/tests/unit/utils/string-test.js new file mode 100644 index 000000000..b719435fe --- /dev/null +++ b/tests/unit/utils/string-test.js @@ -0,0 +1,88 @@ +import { module, test } from 'qunit'; +import { + capitalize, + dasherize, + classify, +} from 'ember-cli-addon-docs/utils/string'; + +module('Unit | Utility | string', function () { + module('capitalize', function () { + test('capitalizes the first letter', function (assert) { + assert.strictEqual(capitalize('hello'), 'Hello'); + assert.strictEqual(capitalize('world'), 'World'); + assert.strictEqual(capitalize('innerHTML'), 'InnerHTML'); + }); + + test('handles already capitalized strings', function (assert) { + assert.strictEqual(capitalize('Hello'), 'Hello'); + }); + + test('handles empty strings', function (assert) { + assert.strictEqual(capitalize(''), ''); + }); + + test('handles non-strings', function (assert) { + assert.strictEqual(capitalize(null), null); + assert.strictEqual(capitalize(undefined), undefined); + }); + }); + + module('dasherize', function () { + test('converts camelCase to kebab-case', function (assert) { + assert.strictEqual(dasherize('innerHTML'), 'inner-html'); + assert.strictEqual(dasherize('actionName'), 'action-name'); + }); + + test('converts spaces to dashes', function (assert) { + assert.strictEqual(dasherize('my favorite items'), 'my-favorite-items'); + }); + + test('converts underscores to dashes', function (assert) { + assert.strictEqual(dasherize('action_name'), 'action-name'); + }); + + test('handles already dasherized strings', function (assert) { + assert.strictEqual(dasherize('css-class-name'), 'css-class-name'); + }); + + test('handles PascalCase', function (assert) { + assert.strictEqual(dasherize('MyComponent'), 'my-component'); + }); + + test('handles non-strings', function (assert) { + assert.strictEqual(dasherize(null), null); + assert.strictEqual(dasherize(undefined), undefined); + }); + }); + + module('classify', function () { + test('converts kebab-case to PascalCase', function (assert) { + assert.strictEqual(classify('css-class-name'), 'CssClassName'); + assert.strictEqual(classify('ember-cli-addon-docs'), 'EmberCliAddonDocs'); + assert.strictEqual(classify('addon-docs'), 'AddonDocs'); + }); + + test('converts snake_case to PascalCase', function (assert) { + assert.strictEqual(classify('action_name'), 'ActionName'); + }); + + test('converts space-separated to PascalCase', function (assert) { + assert.strictEqual(classify('my favorite items'), 'MyFavoriteItems'); + }); + + test('preserves camelCase and capitalizes first letter', function (assert) { + assert.strictEqual(classify('innerHTML'), 'InnerHTML'); + assert.strictEqual(classify('myComponent'), 'MyComponent'); + }); + + test('handles single words', function (assert) { + assert.strictEqual(classify('component'), 'Component'); + assert.strictEqual(classify('Component'), 'Component'); + }); + + test('handles non-strings', function (assert) { + assert.strictEqual(classify(null), null); + assert.strictEqual(classify(undefined), undefined); + }); + }); +});