mirror of
https://github.com/tabler/tabler.git
synced 2026-10-12 05:06:55 +04:00
Rewrite autosize as an Autosize component with the plugin bundled (#3045)
This commit is contained in:
1 parent
1ffbeeca42
commit
a13ebc3621
7 files changed
+199
-27
No files matched your search
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@tabler/core": minor
|
||||
"@tabler/docs": patch
|
||||
"@tabler/preview": patch
|
||||
---
|
||||
|
||||
Added `Autosize` component to `tabler.js` with the `autosize` plugin bundled, plus `update()` and `dispose()` methods.
|
||||
+79
-7
@@ -1,11 +1,83 @@
|
||||
// js-docs-start autosize-init
|
||||
const autosizeElements: NodeListOf<HTMLElement> = document.querySelectorAll<HTMLElement>('[data-bs-toggle="autosize"]')
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Tabler autosize.ts
|
||||
* Licensed under MIT (https://github.com/tabler/tabler/blob/dev/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
if (autosizeElements.length) {
|
||||
autosizeElements.forEach(function (element: HTMLElement) {
|
||||
if (window.autosize) {
|
||||
window.autosize(element)
|
||||
import autosize from 'autosize'
|
||||
|
||||
import BaseComponent from './bootstrap/base-component'
|
||||
import SelectorEngine from './bootstrap/dom/selector-engine'
|
||||
import type { ElementSelector } from './bootstrap/types'
|
||||
|
||||
type ComponentConfig = Record<string, never>
|
||||
|
||||
type ComponentConfigInput = Record<string, unknown>
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'autosize'
|
||||
|
||||
const SELECTOR_DATA_TOGGLE = `[data-bs-toggle="${NAME}"], [data-tblr-toggle="${NAME}"]`
|
||||
|
||||
const Default: ComponentConfig = {}
|
||||
|
||||
const DefaultType: Record<keyof ComponentConfig, string> = {}
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*
|
||||
* Wraps the `autosize` plugin (https://github.com/jackmoore/autosize), bundled
|
||||
* with `tabler.js`.
|
||||
*/
|
||||
|
||||
class Autosize extends BaseComponent {
|
||||
declare _element: HTMLTextAreaElement
|
||||
declare _config: ComponentConfig
|
||||
|
||||
constructor(element: ElementSelector, config?: ComponentConfigInput) {
|
||||
super(element, config)
|
||||
|
||||
if (this._element) {
|
||||
autosize(this._element)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default(): ComponentConfig {
|
||||
return Default
|
||||
}
|
||||
|
||||
static get DefaultType(): Record<keyof ComponentConfig, string> {
|
||||
return DefaultType
|
||||
}
|
||||
|
||||
static get NAME(): string {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Public
|
||||
update(): void {
|
||||
autosize.update(this._element)
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
autosize.destroy(this._element)
|
||||
super.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
// js-docs-start autosize-init
|
||||
for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE)) {
|
||||
Autosize.getOrCreateInstance(element)
|
||||
}
|
||||
// js-docs-end autosize-init
|
||||
|
||||
export default Autosize
|
||||
Vendored
+14
-1
@@ -1,5 +1,18 @@
|
||||
// The `autosize` package ships no type definitions
|
||||
declare module 'autosize' {
|
||||
type AutosizeTarget = Element | ArrayLike<Element>
|
||||
|
||||
interface Autosize {
|
||||
(target: AutosizeTarget): AutosizeTarget
|
||||
update(target: AutosizeTarget): AutosizeTarget
|
||||
destroy(target: AutosizeTarget): AutosizeTarget
|
||||
}
|
||||
|
||||
const autosize: Autosize
|
||||
export default autosize
|
||||
}
|
||||
|
||||
interface Window {
|
||||
autosize?: (element: HTMLElement | HTMLTextAreaElement) => void
|
||||
countUp?: {
|
||||
CountUp: new (
|
||||
target: HTMLElement,
|
||||
|
||||
@@ -12,3 +12,6 @@ import './src/sortable'
|
||||
|
||||
// Re-export everything from bootstrap.ts (single source of truth)
|
||||
export * from './src/bootstrap'
|
||||
|
||||
// Tabler's own components
|
||||
export { default as Autosize } from './src/autosize'
|
||||
@@ -0,0 +1,74 @@
|
||||
import { describe, it, expect, beforeAll, beforeEach, afterEach, vi } from 'vitest'
|
||||
import autosize from 'autosize'
|
||||
import { clearFixture, getFixture } from '../helpers/fixture'
|
||||
import Autosize from '../../src/autosize'
|
||||
|
||||
vi.mock('autosize', () => ({
|
||||
default: Object.assign(vi.fn(), { update: vi.fn(), destroy: vi.fn() }),
|
||||
}))
|
||||
|
||||
const plugin = vi.mocked(autosize)
|
||||
|
||||
describe('Autosize', () => {
|
||||
let fixtureEl: HTMLElement
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
fixtureEl.innerHTML = '<textarea data-bs-toggle="autosize"></textarea>'
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
const textarea = (): HTMLTextAreaElement => fixtureEl.querySelector('textarea')!
|
||||
|
||||
describe('NAME', () => {
|
||||
it('should return plugin name', () => {
|
||||
expect(Autosize.NAME).toBe('autosize')
|
||||
})
|
||||
})
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should call the autosize plugin with the element', () => {
|
||||
const instance = new Autosize(textarea())
|
||||
|
||||
expect(plugin).toHaveBeenCalledWith(textarea())
|
||||
expect(Autosize.getInstance(textarea())).toBe(instance)
|
||||
})
|
||||
})
|
||||
|
||||
describe('update', () => {
|
||||
it('should call autosize.update with the element', () => {
|
||||
const instance = new Autosize(textarea())
|
||||
instance.update()
|
||||
|
||||
expect(plugin.update).toHaveBeenCalledWith(textarea())
|
||||
})
|
||||
})
|
||||
|
||||
describe('dispose', () => {
|
||||
it('should call autosize.destroy and remove the instance', () => {
|
||||
const instance = new Autosize(textarea())
|
||||
const element = textarea()
|
||||
instance.dispose()
|
||||
|
||||
expect(plugin.destroy).toHaveBeenCalledWith(element)
|
||||
expect(Autosize.getInstance(element)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOrCreateInstance', () => {
|
||||
it('should reuse the instance and not initialise the plugin twice', () => {
|
||||
const first = Autosize.getOrCreateInstance(textarea())
|
||||
const second = Autosize.getOrCreateInstance(textarea())
|
||||
|
||||
expect(second).toBe(first)
|
||||
expect(plugin).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,15 +1,11 @@
|
||||
---
|
||||
title: Autosize
|
||||
summary: The autosize element will automatically adjust the textarea height and make it easier for users to follow as they type.
|
||||
docs-libs: [autosize]
|
||||
description: Make textareas grow automatically as users type with the autosize plugin, so longer text stays visible without manual resizing.
|
||||
related: [/ui/forms/elements]
|
||||
---
|
||||
import Example from '@components/Example.astro';
|
||||
import CodeDocs from '@components/CodeDocs.astro';
|
||||
import TabsPackage from '@components/TabsPackage.astro';
|
||||
import { Code } from 'astro:components';
|
||||
import { site } from '@shared/lib/site.ts';
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -21,15 +17,7 @@ A textarea keeps its height no matter how much text it holds, so long answers en
|
||||
|
||||
## Installation
|
||||
|
||||
Install autosize with npm:
|
||||
|
||||
<TabsPackage name="autosize" />
|
||||
|
||||
Or include it from a CDN:
|
||||
|
||||
<Code lang="html" code={`<script src="${site.cdnUrl}/dist/libs/autosize/dist/autosize.min.js"></script>`} />
|
||||
|
||||
The plugin only changes the height of the field, so it needs no CSS of its own.
|
||||
The plugin is bundled with `tabler.js`, so there is nothing extra to install or load. It only changes the height of the field, so it needs no CSS of its own.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -43,22 +31,37 @@ Set `rows` to choose the starting height. With `rows="1"` the field starts as a
|
||||
|
||||
## JavaScript
|
||||
|
||||
Tabler initializes every element with `data-bs-toggle="autosize"` on page load. This is the code that runs:
|
||||
Tabler wraps the plugin in an `Autosize` component that works like the Bootstrap components: it is created once per element and stored on it. On page load Tabler creates one for every element with `data-bs-toggle="autosize"`. This is the code that runs:
|
||||
|
||||
<CodeDocs name="autosize-init" file="core/js/src/autosize.ts" />
|
||||
|
||||
Call the plugin yourself when you add a textarea later, for example inside a modal or a row added by JavaScript:
|
||||
Create the component yourself when you add a textarea later, for example inside a modal or a row added by JavaScript:
|
||||
|
||||
```js
|
||||
autosize(document.getElementById('message'));
|
||||
const autosize = new tabler.Autosize(document.getElementById('message'));
|
||||
```
|
||||
|
||||
Autosize measures the field, so a hidden textarea gets the wrong height. Run `autosize.update()` once it becomes visible:
|
||||
`tabler.Autosize.getOrCreateInstance(element)` returns the existing component or creates a new one, so it is safe to call more than once for the same field.
|
||||
|
||||
Autosize measures the field, so a hidden textarea gets the wrong height. Call `update()` once it becomes visible:
|
||||
|
||||
```js
|
||||
autosize.update(document.getElementById('message'));
|
||||
tabler.Autosize.getOrCreateInstance(document.getElementById('message')).update();
|
||||
```
|
||||
|
||||
Call `dispose()` to stop the plugin and remove the component from the element:
|
||||
|
||||
```js
|
||||
tabler.Autosize.getInstance(document.getElementById('message'))?.dispose();
|
||||
```
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| `update()` | Recalculates the height, for example after the textarea becomes visible or its value changes from code. |
|
||||
| `dispose()` | Stops the plugin and removes the component from the element. |
|
||||
| `getInstance(element)` | Static. Returns the component for the element, or `null`. |
|
||||
| `getOrCreateInstance(element)` | Static. Returns the component for the element and creates it when needed. |
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Keep the `label` for the field. A growing textarea is still a normal form control and needs its name.
|
||||
|
||||
@@ -51,7 +51,7 @@ const rows = [
|
||||
]
|
||||
---
|
||||
|
||||
<DefaultLayout title="Form elements" pageMenu="form.elements" pageLibs={['nouislider', 'autosize', 'tabler-flags', 'tabler-payments', 'litepicker', 'tom-select', 'imask']}>
|
||||
<DefaultLayout title="Form elements" pageMenu="form.elements" pageLibs={['nouislider', 'tabler-flags', 'tabler-payments', 'litepicker', 'tom-select', 'imask']}>
|
||||
<DocsLink slot="page-header-actions" path="/ui/forms/elements" />
|
||||
<div class="row row-cards">
|
||||
<div class="col-12">
|
||||
|
||||
Reference in new issue
Block a user