From f9f18c7e05eff0bf0a1a3ace1b77292fe481f6df Mon Sep 17 00:00:00 2001 From: Robbie Wagner Date: Tue, 28 Jan 2025 08:59:48 -0500 Subject: [PATCH 1/2] Update to Shepherd 14.x --- .prettierrc.js | 8 +++ package-lock.json | 53 +++++++++----- package.json | 3 +- projects/shepherd-tester/src/app/data.ts | 6 +- projects/shepherd/package.json | 2 +- .../shepherd/src/lib/shepherd.service.spec.ts | 12 ++-- projects/shepherd/src/lib/shepherd.service.ts | 71 +++++++++++-------- projects/shepherd/src/lib/utils/buttons.ts | 16 ++++- projects/shepherd/tsconfig.lib.json | 19 ++--- tsconfig.json | 39 +++++----- 10 files changed, 132 insertions(+), 97 deletions(-) create mode 100644 .prettierrc.js diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 000000000..44a5e5c47 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,8 @@ +"use strict"; + +module.exports = { + arrowParens: "always", + proseWrap: "always", + trailingComma: "none", + singleQuote: true, +}; diff --git a/package-lock.json b/package-lock.json index e6aae0a62..31c4cfc94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@angular/platform-browser-dynamic": "^19.0.0", "core-js": "^3.39.0", "rxjs": "^7.8.1", - "shepherd.js": "^11.2.0", + "shepherd.js": "^14.4.0", "tslib": "^2.8.0", "zone.js": "~0.15.0" }, @@ -47,6 +47,7 @@ "karma-jasmine": "^5.1.0", "karma-jasmine-html-reporter": "^2.1.0", "ng-packagr": "^19.0.0", + "prettier": "^3.4.2", "protractor": "~7.0.0", "release-it": "^17.10.0", "ts-node": "~10.9.2", @@ -5470,6 +5471,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true + }, "node_modules/@schematics/angular": { "version": "19.1.3", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-19.1.3.tgz", @@ -9154,13 +9161,12 @@ "dev": true, "license": "MIT" }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "license": "MIT", + "node_modules/deepmerge-ts": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.4.tgz", + "integrity": "sha512-fxqo6nHGQ9zOVgI4KXqtWXJR/yCLtC7aXIVq+6jc8tHPFUxlFmuUcm2kC4vztQ+LJxQ3gER/XAWearGYQ8niGA==", "engines": { - "node": ">=0.10.0" + "node": ">=16.0.0" } }, "node_modules/default-browser": { @@ -17208,6 +17214,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/proc-log": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", @@ -19289,20 +19310,16 @@ } }, "node_modules/shepherd.js": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/shepherd.js/-/shepherd.js-11.2.0.tgz", - "integrity": "sha512-2hbz3N7GuuTjI7y3sfnoqKnH0cNhExx67IJtCTGQI2KhBEyvegsDYW5qjj5BlvvVtQjmL/O/J1GQEciwfoZWpw==", - "license": "MIT", + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/shepherd.js/-/shepherd.js-14.4.0.tgz", + "integrity": "sha512-pXuyhtHPj47Wp6vYESIdhcRrpva67+AolbvVBnJlZNZDa4VBPTwoN+x3R7h1C+RV+z5Tvk3JuLB/8ZgEKMeyEQ==", "dependencies": { - "@floating-ui/dom": "^1.5.1", - "deepmerge": "^4.3.1" + "@floating-ui/dom": "^1.6.12", + "@scarf/scarf": "^1.3.0", + "deepmerge-ts": "^7.1.1" }, "engines": { - "node": "16.* || >= 18" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/RobbieTheWagner" + "node": "18.* || >= 20" } }, "node_modules/side-channel": { diff --git a/package.json b/package.json index b6ad77469..3ab67b97c 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@angular/platform-browser-dynamic": "^19.0.0", "core-js": "^3.39.0", "rxjs": "^7.8.1", - "shepherd.js": "^11.2.0", + "shepherd.js": "^14.4.0", "tslib": "^2.8.0", "zone.js": "~0.15.0" }, @@ -58,6 +58,7 @@ "karma-jasmine": "^5.1.0", "karma-jasmine-html-reporter": "^2.1.0", "ng-packagr": "^19.0.0", + "prettier": "^3.4.2", "protractor": "~7.0.0", "release-it": "^17.10.0", "ts-node": "~10.9.2", diff --git a/projects/shepherd-tester/src/app/data.ts b/projects/shepherd-tester/src/app/data.ts index ec8ce27d6..d05e3f9a5 100644 --- a/projects/shepherd-tester/src/app/data.ts +++ b/projects/shepherd-tester/src/app/data.ts @@ -1,4 +1,4 @@ -import Step from 'shepherd.js/src/types/step'; +import { type StepOptions } from 'shepherd.js'; export const builtInButtons = { cancel: { @@ -20,7 +20,7 @@ export const builtInButtons = { } }; -export const defaultStepOptions: Step.StepOptions = { +export const defaultStepOptions: StepOptions = { classes: 'shepherd-theme-arrows custom-default-class', scrollTo: true, cancelIcon: { @@ -28,7 +28,7 @@ export const defaultStepOptions: Step.StepOptions = { } }; -export const steps: Step.StepOptions[] = [ +export const steps: StepOptions[] = [ { attachTo: { element: '.first-element', diff --git a/projects/shepherd/package.json b/projects/shepherd/package.json index 7aa7ea031..ed1bcffb0 100644 --- a/projects/shepherd/package.json +++ b/projects/shepherd/package.json @@ -20,7 +20,7 @@ "url": "https://github.com/rwwagner90" }, "dependencies": { - "shepherd.js": "^11.0.0" + "shepherd.js": "^14.4.0" }, "peerDependencies": { "@angular/common": "^19.0.0", diff --git a/projects/shepherd/src/lib/shepherd.service.spec.ts b/projects/shepherd/src/lib/shepherd.service.spec.ts index 30b0336ce..77dc5b90c 100644 --- a/projects/shepherd/src/lib/shepherd.service.spec.ts +++ b/projects/shepherd/src/lib/shepherd.service.spec.ts @@ -29,10 +29,7 @@ const steps = [ element: '.test-element', on: 'bottom' }, - buttons: [ - builtInButtons.cancel, - builtInButtons.next - ], + buttons: [builtInButtons.cancel, builtInButtons.next], classes: 'custom-class-name-1 custom-class-name-2', title: 'Welcome to Ember-Shepherd!', text: 'Test text', @@ -53,7 +50,7 @@ describe('ShepherdService', () => { start() { expect(true).toBeTruthy('The tour was started'); } - } + }; service.addSteps(steps); @@ -67,7 +64,10 @@ describe('ShepherdService', () => { const service: ShepherdService = TestBed.inject(ShepherdService); const mockTourObject = { start() { - expect(steps[0].options.scrollToHandler()).toBe('custom scrollToHandler', 'The handler was passed through as an option on the step'); + expect(steps[0]?.options.scrollToHandler()).toBe( + 'custom scrollToHandler', + 'The handler was passed through as an option on the step' + ); } }; diff --git a/projects/shepherd/src/lib/shepherd.service.ts b/projects/shepherd/src/lib/shepherd.service.ts index 472e798e4..97ceadcf3 100644 --- a/projects/shepherd/src/lib/shepherd.service.ts +++ b/projects/shepherd/src/lib/shepherd.service.ts @@ -1,61 +1,65 @@ import { Injectable } from '@angular/core'; -import Shepherd from 'shepherd.js'; +import { Tour, type TourOptions, type StepOptions } from 'shepherd.js'; import { elementIsHidden } from './utils/dom'; import { makeButton } from './utils/buttons'; -import Step from 'shepherd.js/src/types/step'; + +interface RequiredElement { + message: string; + selector: string; + title: string; +} @Injectable({ providedIn: 'root' }) export class ShepherdService { - confirmCancel = false; - confirmCancelMessage: string = null; - defaultStepOptions: Step.StepOptions = {}; - errorTitle = null; + confirmCancel: TourOptions['confirmCancel'] = false; + confirmCancelMessage?: TourOptions['confirmCancelMessage']; + defaultStepOptions: StepOptions = {}; + errorTitle?: string; + exitOnEsc = true; isActive = false; - keyboardNavigation = true; - messageForUser: string = null; + keyboardNavigation: TourOptions['keyboardNavigation'] = true; + messageForUser: string | null = null; modal = false; - requiredElements = []; + requiredElements: Array = []; tourName = undefined; - tourObject: Shepherd.Tour = null; - exitOnEsc = true; + tourObject: Tour | null = null; - constructor () { - } + constructor() {} /** * Get the tour object and call back */ back() { - this.tourObject.back(); + this.tourObject?.back(); } /** * Cancel the tour */ cancel() { - this.tourObject.cancel(); + this.tourObject?.cancel(); } /** * Complete the tour */ complete() { - this.tourObject.complete(); + this.tourObject?.complete(); } /** * Hides the current step */ hide() { - this.tourObject.hide(); + this.tourObject?.hide(); } /** * Advance the tour to the next step */ next() { - this.tourObject.next(); + this.tourObject?.next(); } /** @@ -63,7 +67,7 @@ export class ShepherdService { * @param id The id of the step you want to show */ show(id: string | number) { - this.tourObject.show(id); + this.tourObject?.show(id); } /** @@ -71,7 +75,7 @@ export class ShepherdService { */ start() { this.isActive = true; - this.tourObject.start(); + this.tourObject?.start(); } /** @@ -86,24 +90,26 @@ export class ShepherdService { * Take a set of steps and create a tour object based on the current configuration * @param steps An array of steps */ - addSteps(steps: Array) { + addSteps(steps: Array) { this._initialize(); const tour = this.tourObject; - // Return nothing if there are no steps - if (!steps || !Array.isArray(steps) || steps.length === 0) { + // Return nothing if there are no steps or if somehow there is no tour. + if (!tour || !steps || !Array.isArray(steps) || steps.length === 0) { return; } if (!this.requiredElementsPresent()) { tour.addStep({ - buttons: [{ - text: 'Exit', - action: tour.cancel - }], + buttons: [ + { + text: 'Exit', + action: tour.cancel + } + ], id: 'error', title: this.errorTitle, - text: [this.messageForUser] + text: [this.messageForUser as string] }); return; } @@ -128,7 +134,10 @@ export class ShepherdService { this.requiredElements.forEach((element) => { const selectedElement = document.querySelector(element.selector); - if (allElementsPresent && (!selectedElement || elementIsHidden(selectedElement))) { + if ( + allElementsPresent && + (!selectedElement || elementIsHidden(selectedElement as HTMLElement)) + ) { allElementsPresent = false; this.errorTitle = element.title; this.messageForUser = element.message; @@ -142,7 +151,7 @@ export class ShepherdService { * Initializes the tour, creates a new Shepherd.Tour. sets options, and binds events */ private _initialize() { - const tourObject = new Shepherd.Tour({ + const tourObject = new Tour({ confirmCancel: this.confirmCancel, confirmCancelMessage: this.confirmCancelMessage, defaultStepOptions: this.defaultStepOptions, @@ -155,6 +164,6 @@ export class ShepherdService { tourObject.on('complete', this.onTourFinish.bind(this, 'complete')); tourObject.on('cancel', this.onTourFinish.bind(this, 'cancel')); - this.tourObject = tourObject; + this.tourObject = tourObject as Tour; } } diff --git a/projects/shepherd/src/lib/utils/buttons.ts b/projects/shepherd/src/lib/utils/buttons.ts index 48c7e3525..1a8d800ed 100644 --- a/projects/shepherd/src/lib/utils/buttons.ts +++ b/projects/shepherd/src/lib/utils/buttons.ts @@ -1,3 +1,10 @@ +import type { StepOptionsButton } from 'shepherd.js'; +import type { ShepherdService } from '../shepherd.service'; + +export type AngularShepherdButton = StepOptionsButton & { + type?: 'back' | 'cancel' | 'next'; +}; + /** * Creates a button of the specified type, with the given classes and text * @@ -6,7 +13,10 @@ * @param button.text The text for the button * @param button.action The action to call */ -export function makeButton(button) { +export function makeButton( + this: ShepherdService, + button: AngularShepherdButton +) { const { classes, disabled, label, secondary, type, text } = button; const builtInButtonTypes = ['back', 'cancel', 'next']; @@ -15,7 +25,9 @@ export function makeButton(button) { } if (builtInButtonTypes.indexOf(type) === -1) { - throw new Error(`'type' property must be one of 'back', 'cancel', or 'next'`); + throw new Error( + `'type' property must be one of 'back', 'cancel', or 'next'` + ); } return { diff --git a/projects/shepherd/tsconfig.lib.json b/projects/shepherd/tsconfig.lib.json index 4d5a3fcd8..167a35f43 100644 --- a/projects/shepherd/tsconfig.lib.json +++ b/projects/shepherd/tsconfig.lib.json @@ -1,20 +1,13 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "outDir": "../../out-tsc/lib", - "module": "es2015", - "moduleResolution": "node", "declaration": true, - "sourceMap": true, - "inlineSources": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, - "importHelpers": true, - "types": [], - "lib": [ - "dom", - "es2018" - ] + "outDir": "../../out-tsc/lib", + "sourceMap": true, + "inlineSources": true, + "types": [] }, "angularCompilerOptions": { "annotateForClosureCompiler": true, @@ -25,7 +18,5 @@ "strictInjectionParameters": true, "enableResourceInlining": true }, - "exclude": [ - "**/*.spec.ts" - ] + "exclude": ["**/*.spec.ts"] } diff --git a/tsconfig.json b/tsconfig.json index 0696e559a..a925e3bec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,31 +1,28 @@ { "compileOnSave": false, "compilerOptions": { + "allowArbitraryExtensions": true, + "allowSyntheticDefaultImports": false, "baseUrl": "./", - "outDir": "./dist/out-tsc", - "sourceMap": true, "declaration": false, - "module": "es2015", - "moduleResolution": "node", "emitDecoratorMetadata": true, + "esModuleInterop": true, "experimentalDecorators": true, "importHelpers": true, + "inlineSourceMap": true, + "inlineSources": true, + "lib": ["es2018", "dom"], + "module": "ESNext", + "moduleResolution": "bundler", + "outDir": "./dist/out-tsc", "target": "ES2022", - "typeRoots": [ - "node_modules/@types" - ], - "lib": [ - "es2018", - "dom" - ], - "paths": { - "shepherd": [ - "dist/shepherd" - ], - "shepherd/*": [ - "dist/shepherd/*" - ] - }, - "useDefineForClassFields": false + "typeRoots": ["node_modules/@types"], + "useDefineForClassFields": false, + // We don't want to include types dependencies in our compiled output, so tell TypeScript + // to enforce using `import type` instead of `import` for Types. + "verbatimModuleSyntax": true, + "allowJs": true, + "strict": true, + "noUncheckedIndexedAccess": true } -} \ No newline at end of file +} From 3bf44044b4c7572cfef4a9b41ffaad7dd1d3ee8a Mon Sep 17 00:00:00 2001 From: Robbie Wagner Date: Tue, 28 Jan 2025 09:14:40 -0500 Subject: [PATCH 2/2] Fix TS and tests --- README.md | 2 +- .../src/app/shepherd/shepherd.component.ts | 9 ++++----- projects/shepherd/README.md | 2 +- projects/shepherd/src/lib/shepherd.service.ts | 10 +++++++--- tsconfig.json | 4 ---- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index dd16cae37..17753fcd0 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ at the app level ensures you only create one instance of Shepherd.** In that component you will want to use `AfterViewInit` to `addSteps` to the Shepherd service. ```typescript -import { Component, AfterViewInit } from '@angular/core'; +import { Component, type AfterViewInit } from '@angular/core'; import { ShepherdService } from 'angular-shepherd'; import { steps as defaultSteps, defaultStepOptions} from '../data'; diff --git a/projects/shepherd-tester/src/app/shepherd/shepherd.component.ts b/projects/shepherd-tester/src/app/shepherd/shepherd.component.ts index e98481c70..8e0b002a3 100644 --- a/projects/shepherd-tester/src/app/shepherd/shepherd.component.ts +++ b/projects/shepherd-tester/src/app/shepherd/shepherd.component.ts @@ -1,16 +1,15 @@ -import { Component, AfterViewInit } from '@angular/core'; +import { Component, type AfterViewInit } from '@angular/core'; import { ShepherdService } from '../../../../shepherd/src/lib/shepherd.service'; -import { steps as defaultSteps, defaultStepOptions} from '../data'; +import { steps as defaultSteps, defaultStepOptions } from '../data'; @Component({ selector: 'app-shepherd', templateUrl: './shepherd.component.html', styleUrls: ['./shepherd.component.css'], - standalone: true, + standalone: true }) export class ShepherdComponent implements AfterViewInit { - - constructor(private shepherdService: ShepherdService) { } + constructor(private shepherdService: ShepherdService) {} ngAfterViewInit() { this.shepherdService.defaultStepOptions = defaultStepOptions; diff --git a/projects/shepherd/README.md b/projects/shepherd/README.md index 6756abe20..f4095b2cc 100644 --- a/projects/shepherd/README.md +++ b/projects/shepherd/README.md @@ -59,7 +59,7 @@ at the app level ensures you only create one instance of Shepherd.** In that component you will want to use `AfterViewInit` to `addSteps` to the Shepherd service. ```typescript -import { Component, AfterViewInit } from '@angular/core'; +import { Component, type AfterViewInit } from '@angular/core'; import { ShepherdService } from 'angular-shepherd'; import { steps as defaultSteps, defaultStepOptions} from '../data'; diff --git a/projects/shepherd/src/lib/shepherd.service.ts b/projects/shepherd/src/lib/shepherd.service.ts index 97ceadcf3..9fc327783 100644 --- a/projects/shepherd/src/lib/shepherd.service.ts +++ b/projects/shepherd/src/lib/shepherd.service.ts @@ -1,5 +1,9 @@ import { Injectable } from '@angular/core'; -import { Tour, type TourOptions, type StepOptions } from 'shepherd.js'; +import Shepherd, { + type TourOptions, + type StepOptions, + type Tour +} from 'shepherd.js'; import { elementIsHidden } from './utils/dom'; import { makeButton } from './utils/buttons'; @@ -151,7 +155,7 @@ export class ShepherdService { * Initializes the tour, creates a new Shepherd.Tour. sets options, and binds events */ private _initialize() { - const tourObject = new Tour({ + const tourObject = new Shepherd.Tour({ confirmCancel: this.confirmCancel, confirmCancelMessage: this.confirmCancelMessage, defaultStepOptions: this.defaultStepOptions, @@ -164,6 +168,6 @@ export class ShepherdService { tourObject.on('complete', this.onTourFinish.bind(this, 'complete')); tourObject.on('cancel', this.onTourFinish.bind(this, 'cancel')); - this.tourObject = tourObject as Tour; + this.tourObject = tourObject; } } diff --git a/tsconfig.json b/tsconfig.json index a925e3bec..4aeaf90bb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,8 +9,6 @@ "esModuleInterop": true, "experimentalDecorators": true, "importHelpers": true, - "inlineSourceMap": true, - "inlineSources": true, "lib": ["es2018", "dom"], "module": "ESNext", "moduleResolution": "bundler", @@ -21,8 +19,6 @@ // We don't want to include types dependencies in our compiled output, so tell TypeScript // to enforce using `import type` instead of `import` for Types. "verbatimModuleSyntax": true, - "allowJs": true, "strict": true, - "noUncheckedIndexedAccess": true } }