From 7ede916f653f6a5c1cd77b81ecf2075f42ec3235 Mon Sep 17 00:00:00 2001 From: mhmo91 Date: Thu, 16 Jan 2025 15:09:23 +0100 Subject: [PATCH] . --- .github/workflows/publish-npm-packages.yml | 2 +- demo/src/features/typewriter.ts | 25 +- demo/src/nav.ts | 2 +- package-lock.json | 6 +- package.json | 2 +- readme.md | 1 + src/area/area.component.ts | 2 +- src/content-drawer/sheet.ts | 8 +- src/nav-drawer/drawer.ts | 12 +- src/nav-drawer/navbar.ts | 10 +- src/slider/slider.ts | 1 - src/tree/tree.ts | 19 +- src/typewriter/typewriter.ts | 284 +++++---------------- types/src/index.d.ts | 2 + types/src/nav-drawer/drawer.d.ts | 4 +- types/src/typewriter/index.d.ts | 1 + types/src/typewriter/typewriter.d.ts | 67 +++++ 17 files changed, 168 insertions(+), 280 deletions(-) create mode 100644 types/src/typewriter/index.d.ts create mode 100644 types/src/typewriter/typewriter.d.ts diff --git a/.github/workflows/publish-npm-packages.yml b/.github/workflows/publish-npm-packages.yml index 6f8cff4..be0d20e 100644 --- a/.github/workflows/publish-npm-packages.yml +++ b/.github/workflows/publish-npm-packages.yml @@ -30,6 +30,6 @@ jobs: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Publish to npm - run: bun i && bun run build && npm publish --access public + run: npm i && npm run build && npm publish --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/demo/src/features/typewriter.ts b/demo/src/features/typewriter.ts index 4d1ca02..13ca261 100644 --- a/demo/src/features/typewriter.ts +++ b/demo/src/features/typewriter.ts @@ -6,24 +6,13 @@ import { customElement } from 'lit/decorators.js' export class SchmancyTypewriterDemo extends $LitElement() { render() { return html` - - - + + Hello, world! + + Welcome to Schmancy. + + Lit Components! + ` } } diff --git a/demo/src/nav.ts b/demo/src/nav.ts index 4bb2f9a..754697c 100644 --- a/demo/src/nav.ts +++ b/demo/src/nav.ts @@ -116,7 +116,7 @@ export class DemoNav extends $LitElement(css` return html` - + Schmancy diff --git a/package-lock.json b/package-lock.json index 62da83b..a875cca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@mhmo91/schmancy", - "version": "0.0.285", + "version": "0.0.286", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@mhmo91/schmancy", - "version": "0.0.285", + "version": "0.0.286", "license": "Apache-2.0", "dependencies": { "@floating-ui/dom": "^1.6.13", @@ -20,7 +20,7 @@ "@types/cleave.js": "^1.4.12", "cleave.js": "^1.6.0", "fastest-levenshtein": "^1.0.16", - "lit": "^3.2.1", + "lit": "^3.2.0", "moment": "^2.30.1", "playground-elements": "^0.19.1", "rxjs": "latest", diff --git a/package.json b/package.json index a2fcfd8..0d69aa0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mhmo91/schmancy", - "version": "0.0.286", + "version": "0.0.287", "description": "UI library build with web components", "main": "./dist/index.js", "repository": { diff --git a/readme.md b/readme.md index 4099e5a..b95a565 100644 --- a/readme.md +++ b/readme.md @@ -62,6 +62,7 @@ Below is a table linking to each component’s documentation on the GitHub Wiki: | Component | Description | Documentation | | ------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------- | | `schmancy-area` | Area component | [Wiki - schmancy-area](https://github.com/mhmo91/schmancy/wiki/schmancy-area) | +| `schmancy-typewriter` | Typewriter component | [Wiki - schmancy-area](https://github.com/mhmo91/schmancy/wiki/schmancy-typewriter) | | `schmancy-autocomplete` | Autocomplete field | [Wiki - schmancy-autocomplete](https://github.com/mhmo91/schmancy/wiki/schmancy-autocomplete) | | `schmancy-busy` | Loading/busy indicator | [Wiki - schmancy-busy](https://github.com/mhmo91/schmancy/wiki/schmancy-busy) | | `schmancy-button` | Customizable button | [Wiki - schmancy-button](https://github.com/mhmo91/schmancy/wiki/schmancy-button) | diff --git a/src/area/area.component.ts b/src/area/area.component.ts index d617484..a3905d8 100644 --- a/src/area/area.component.ts +++ b/src/area/area.component.ts @@ -160,7 +160,7 @@ export class SchmancyArea extends $LitElement(css` // Native Web Animations API - fade in // "ease: cubic-bezier(0.25, 0.8, 0.25, 1)" was used in the old code - component.animate([{ opacity: '0' }, { opacity: '1' }], { + component.animate([{ opacity: 0 }, { opacity: 1 }], { duration: oldViewExists ? 250 : 150, easing: 'cubic-bezier(0.25, 0.8, 0.25, 1)', }) diff --git a/src/content-drawer/sheet.ts b/src/content-drawer/sheet.ts index e96050f..0ac0a35 100644 --- a/src/content-drawer/sheet.ts +++ b/src/content-drawer/sheet.ts @@ -96,8 +96,8 @@ export class SchmancyContentDrawerSheet extends $LitElement(css` // --- 2) Use native Web Animations API --- this.sheet.animate( [ - { opacity: '0', transform: 'translateX(100%)' }, - { opacity: '1', transform: 'translateX(0%)' }, + { opacity: 0, transform: 'translateX(100%)' }, + { opacity: 1, transform: 'translateX(0%)' }, ], { duration: 500, @@ -133,8 +133,8 @@ export class SchmancyContentDrawerSheet extends $LitElement(css` return new Observable(observer => { const animation = this.sheet.animate( [ - { opacity: '1', transform: 'translateX(0%)' }, - { opacity: '1', transform: 'translateX(100%)' }, + { opacity: 1, transform: 'translateX(0%)' }, + { opacity: 1, transform: 'translateX(100%)' }, ], { duration: 500, diff --git a/src/nav-drawer/drawer.ts b/src/nav-drawer/drawer.ts index 19f9698..18946ac 100644 --- a/src/nav-drawer/drawer.ts +++ b/src/nav-drawer/drawer.ts @@ -31,12 +31,12 @@ export class SchmancyNavigationDrawer extends $LitElement(css` /** * The minimum width of the sidebar - * @attr min-width + * @attr breakpoint * @type {number} * @memberof SchmancyNavigationDrawer */ - @property({ type: Number }) - minWidth: number = 1240 + @property({ type: Number, attribute: 'breakpoint' }) + breakpoint: number = 768 /** * The mode of the sidebar @@ -59,7 +59,7 @@ export class SchmancyNavigationDrawer extends $LitElement(css` map(event => event.target as Window), startWith(window), map(window => window.innerWidth), - map(width => width >= this.minWidth), + map(width => width >= this.breakpoint), distinctUntilChanged(), takeUntil(this.disconnecting), debounceTime(100), @@ -96,8 +96,8 @@ export class SchmancyNavigationDrawer extends $LitElement(css` return html` { diff --git a/src/typewriter/typewriter.ts b/src/typewriter/typewriter.ts index 3b936d0..7e24f9d 100644 --- a/src/typewriter/typewriter.ts +++ b/src/typewriter/typewriter.ts @@ -1,49 +1,8 @@ import { $LitElement } from '@mixins/index' import { css, html, TemplateResult } from 'lit' import { customElement, property, query } from 'lit/decorators.js' -import { BehaviorSubject, distinctUntilChanged, startWith, Subscription } from 'rxjs' import TypeIt, { Options as TypeItOptions } from 'typeit' -import { CursorOptions } from 'typeit/dist/types' -/** - * Type definition for typing actions. - * - Strings represent 'type' actions. - * - Objects represent control actions like 'pause', 'delete', etc. - */ -type TypingAction = - | string - | { - action: 'pause' | 'delete' | 'reset' | 'options' | string - value?: string | number | TypeItOptions - } - -/** - * A highly configurable typewriter component using Lit, TypeIt, and RxJS. - * - * Usage: - * - */ @customElement('schmancy-typewriter') export class TypewriterElement extends $LitElement(css` :host { @@ -52,34 +11,13 @@ export class TypewriterElement extends $LitElement(css` #typewriter { white-space: nowrap; + --ti-cursor-display: initial; } - /* Optional: Customize cursor styles */ - .typeit-cursor { - display: inline-block; - animation: blink 1s infinite; - } - - @keyframes blink { - 0% { - opacity: 1; - } - 50% { - opacity: 0; - } - 100% { - opacity: 1; - } + #typewriter .ti-cursor { + display: var(--ti-cursor-display); } `) { - /** - * Array of typing actions to execute in sequence. - * - Strings represent 'type' actions. - * - Objects represent control actions like 'pause', 'delete', etc. - */ - @property({ type: Array }) - actions: TypingAction[] = [] - /** * Typing speed in milliseconds per character. */ @@ -100,74 +38,21 @@ export class TypewriterElement extends $LitElement(css` /** * Whether to show the cursor. - * Can be a boolean or CursorOptions object. */ - @property({ type: Object }) - cursor: boolean | CursorOptions = true + @property({ type: Boolean }) + cursor: boolean = true /** * The cursor character. */ @property({ type: String }) - cursorChar: string = '|' - - /** - * Whether the typing should loop. - */ - @property({ type: Boolean }) - loop: boolean = false - - /** - * Delay before restarting the loop (ms or array of ms). - */ - @property({ type: Number }) - loopDelay: number | number[] = 2500 + cursorChar: string = '' /** * Typing speed for deletions (ms per character). */ @property({ type: Number }) - deleteSpeed: number | null = 30 - - /** - * Pause duration in milliseconds between typing and deleting. - */ - @property({ type: Number }) - pause: number = 500 - - /** - * Automatically break lines on newline characters. - */ - @property({ type: Boolean }) - breakLines: boolean = true - - /** - * Simulate human-like typing with variable speed and errors. - */ - @property({ type: Boolean }) - lifeLike: boolean = false - - /** - * Whether to wait until the element is visible before typing. - */ - @property({ type: Boolean }) - waitUntilVisible: boolean = true - - /** - * Delay before typing the next string (ms or array of ms). - */ - @property({ type: Number }) - nextStringDelay: number | number[] = 750 - - /** - * Internal BehaviorSubject to manage action changes. - */ - private actions$ = new BehaviorSubject([]) - - /** - * Subscription for the actions BehaviorSubject. - */ - private actionsSubscription: Subscription | null = null + deleteSpeed: number = 30 /** * TypeIt instance. @@ -180,46 +65,19 @@ export class TypewriterElement extends $LitElement(css` @query('#typewriter') private typewriterContainer!: HTMLElement - /** - * Flag to prevent multiple reset calls. - */ - private isResetting: boolean = false - - /** - * Lifecycle method called after the component's DOM has been updated the first time. - */ - firstUpdated() { - this.actionsSubscription = this.actions$ - .pipe( - startWith(this.actions), - distinctUntilChanged((prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)), - ) - .subscribe(newActions => { - this._startTyping(newActions) - }) - } - /** * Lifecycle method called when the component is disconnected from the DOM. - * Ensures that subscriptions and TypeIt instances are properly cleaned up. + * Ensures that TypeIt instances are properly cleaned up. */ disconnectedCallback() { super.disconnectedCallback() - if (this.actionsSubscription) { - this.actionsSubscription.unsubscribe() - } this._destroyTypeIt() } /** - * Initializes or restarts the TypeIt instance with the provided actions. + * Initializes the TypeIt instance with the provided slotted content. */ - private _startTyping(actions: TypingAction[]) { - // Prevent starting typing while resetting - if (this.isResetting) { - return - } - + private _startTyping() { // Destroy any existing TypeIt instance this._destroyTypeIt() @@ -228,68 +86,34 @@ export class TypewriterElement extends $LitElement(css` return } - // Configure TypeIt options based on component properties + // Configure TypeIt options const typeItOptions: TypeItOptions = { speed: this.speed, startDelay: this.startDelay, - cursor: this.cursor === true ? true : this.cursor, // If cursor is object, use it; else, boolean + cursor: this.cursor, cursorChar: this.cursorChar, - loop: this.loop, - loopDelay: this.loopDelay, deleteSpeed: this.deleteSpeed, - breakLines: this.breakLines, - lifeLike: this.lifeLike, - waitUntilVisible: this.waitUntilVisible, - nextStringDelay: this.nextStringDelay, afterComplete: () => { + // Dispatch the custom event this.dispatchEvent(new CustomEvent('typeit-complete', { bubbles: true, composed: true })) - if (!this.loop) { - this._destroyTypeIt() - } + + // Hide the cursor + this.typewriterContainer.style.setProperty('--ti-cursor-display', 'none') }, } // Initialize TypeIt this.typeItInstance = new TypeIt(this.typewriterContainer, typeItOptions) - // Process each action - actions.forEach(action => { - if (typeof action === 'string') { - // Treat as 'type' action - this.typeItInstance?.type(action) - } else { - // Control actions - switch (action.action) { - case 'pause': - if (typeof action.value === 'number') { - this.typeItInstance?.pause(action.value) - } else { - console.warn('Value for "pause" action must be a number (milliseconds).') - } - break - case 'delete': - if (typeof action.value === 'number') { - this.typeItInstance?.delete(action.value) - } else { - console.warn('Value for "delete" action must be a number (characters to delete).') - } - break - case 'options': - if (typeof action.value === 'object' && !Array.isArray(action.value)) { - this.typeItInstance?.options(action.value as TypeItOptions) - } else { - console.warn('Value for "options" action must be a TypeItOptions object.') - } - break - case 'reset': - // Handle reset by scheduling it to prevent immediate recursive calls - setTimeout(() => { - this.reset() - }, 0) - break - default: - console.warn(`Unknown action: ${action.action}`) - } + // Process slotted content as actions + const slottedNodes = this._getSlottedNodes() + slottedNodes.forEach(node => { + if (node.nodeType === Node.TEXT_NODE) { + // Handle plain text + this.typeItInstance?.type(node.textContent || '') + } else if (node instanceof HTMLElement) { + // Handle custom element + this._processCustomElement(node) } }) @@ -310,44 +134,58 @@ export class TypewriterElement extends $LitElement(css` console.error('Error destroying TypeIt instance:', error) } this.typeItInstance = null - this.dispatchEvent(new CustomEvent('typeit-destroy', { bubbles: true, composed: true })) } } /** - * Exposes a method to manually start the typing animation. + * Gets the assigned slotted nodes and removes them from rendering. */ - public start() { - this.actions$.next(this.actions) - } + private _getSlottedNodes(): Node[] { + const slot = this.shadowRoot?.querySelector('slot') + const nodes = slot ? slot.assignedNodes({ flatten: true }) : [] - /** - * Exposes a method to manually stop the typing animation. - */ - public stop() { - this._destroyTypeIt() + // Remove all nodes from rendering + nodes.forEach(node => { + if (node instanceof HTMLElement || node.nodeType === Node.TEXT_NODE) { + // node.remove() + } + }) + + return nodes } /** - * Exposes a method to reset the typing animation. - */ - public reset() { - if (this.isResetting) { - return - } - this.isResetting = true - this._destroyTypeIt() - if (this.autoStart) { - this.start() + * Processes a custom element for its typing behavior. + */ + private _processCustomElement(element: HTMLElement) { + const action = element.getAttribute('action') + const value = element.getAttribute('value') + switch (action) { + case 'pause': + this.typeItInstance?.pause(parseInt(value || '0', 10)) + break + case 'delete': + this.typeItInstance?.delete(parseInt(value || '0', 10)) + break + default: + // Treat as text if no action is defined + this.typeItInstance?.type(element.textContent || '') + break } - this.isResetting = false } /** * Renders the component's HTML. */ render(): TemplateResult { - return html`
` + return html`
+ + ` } } diff --git a/types/src/index.d.ts b/types/src/index.d.ts index 61cd253..e8d8085 100644 --- a/types/src/index.d.ts +++ b/types/src/index.d.ts @@ -9,6 +9,7 @@ export * from './chips'; export * from './components'; export * from './content-drawer'; export * from './data'; +export * from './date-range'; export * from './directives'; export * from './divider'; export * from './form'; @@ -34,4 +35,5 @@ export * from './theme'; export * from './theme-button'; export * from './tree'; export * from './types'; +export * from './typewriter'; export * from './typography'; diff --git a/types/src/nav-drawer/drawer.d.ts b/types/src/nav-drawer/drawer.d.ts index 318c5d9..eb36ef0 100644 --- a/types/src/nav-drawer/drawer.d.ts +++ b/types/src/nav-drawer/drawer.d.ts @@ -10,11 +10,11 @@ export declare class SchmancyNavigationDrawer extends SchmancyNavigationDrawer_b fullscreen: boolean; /** * The minimum width of the sidebar - * @attr min-width + * @attr breakpoint * @type {number} * @memberof SchmancyNavigationDrawer */ - minWidth: number; + breakpoint: number; /** * The mode of the sidebar * @type {TSchmancyDrawerNavbarMode} diff --git a/types/src/typewriter/index.d.ts b/types/src/typewriter/index.d.ts new file mode 100644 index 0000000..eb9acd8 --- /dev/null +++ b/types/src/typewriter/index.d.ts @@ -0,0 +1 @@ +export * from './typewriter'; diff --git a/types/src/typewriter/typewriter.d.ts b/types/src/typewriter/typewriter.d.ts new file mode 100644 index 0000000..445db20 --- /dev/null +++ b/types/src/typewriter/typewriter.d.ts @@ -0,0 +1,67 @@ +import { TemplateResult } from 'lit'; +declare const TypewriterElement_base: CustomElementConstructor & import("@mixins/index").Constructor & import("@mixins/index").Constructor; +export declare class TypewriterElement extends TypewriterElement_base { + /** + * Typing speed in milliseconds per character. + */ + speed: number; + /** + * Delay before typing starts (ms). + */ + startDelay: number; + /** + * Automatically start typing on initialization. + */ + autoStart: boolean; + /** + * Whether to show the cursor. + */ + cursor: boolean; + /** + * The cursor character. + */ + cursorChar: string; + /** + * Typing speed for deletions (ms per character). + */ + deleteSpeed: number; + /** + * TypeIt instance. + */ + private typeItInstance; + /** + * Reference to the typewriter container. + */ + private typewriterContainer; + /** + * Lifecycle method called when the component is disconnected from the DOM. + * Ensures that TypeIt instances are properly cleaned up. + */ + disconnectedCallback(): void; + /** + * Initializes the TypeIt instance with the provided slotted content. + */ + private _startTyping; + /** + * Destroys the current TypeIt instance if it exists. + */ + private _destroyTypeIt; + /** + * Gets the assigned slotted nodes and removes them from rendering. + */ + private _getSlottedNodes; + /** + * Processes a custom element for its typing behavior. + */ + private _processCustomElement; + /** + * Renders the component's HTML. + */ + render(): TemplateResult; +} +declare global { + interface HTMLElementTagNameMap { + 'schmancy-typewriter': TypewriterElement; + } +} +export {};