mirror of
https://github.com/archtechx/alpine-typescript.git
synced 2025-12-11 22:34:03 +00:00
initial commit
This commit is contained in:
commit
1260642e07
5 changed files with 298 additions and 0 deletions
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Samuel Štancl
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
160
README.md
Normal file
160
README.md
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
# TypeScript support for Alpine.js
|
||||
|
||||
This package comes with a light TypeScript layer which provides full support for class components in Alpine.js.
|
||||
|
||||
It's used like this:
|
||||
|
||||
**Register a component**
|
||||
```ts
|
||||
import { DarkModeToggle }
|
||||
|
||||
Alpine.component('DarkModeToggle', DarkModeToggle);
|
||||
```
|
||||
|
||||
**Use it the template**
|
||||
```html
|
||||
<div x-data="Alpine.component('DarkModeToggle')()" x-init="init()">
|
||||
<button type="button" @click="switchTheme()">Switch theme</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
npm install --save-dev github:leanadmin/alpine-typescript
|
||||
```
|
||||
|
||||
```ts
|
||||
// todo
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
You can get a component by calling `Alpine.component('component-name')(arg1, arg2)`. If your component has no arguments, still append the `()` after the call.
|
||||
|
||||
The `component()` call itself returns a function that creates an instance of the component. Invoking the function ensures that the component has a unique instance each time.
|
||||
|
||||
```html
|
||||
<div x-data="Alpine.component('DarkModeToggle')()" x-init="init()">
|
||||
<button type="button" @click="switchTheme()">Switch theme</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
```html
|
||||
<div x-data="Alpine.component('SearchableSelect')({ options: ['Foo', 'Bar'] })" x-init="init()">
|
||||
<div x-spread="options">
|
||||
...
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Creating components
|
||||
|
||||
To create a component, you need to create the component object and register it using one of the provided helpers.
|
||||
|
||||
Component objects can be:
|
||||
- functions returning plain objects
|
||||
- classes
|
||||
|
||||
In the context of plain objects, the wrapper function acts as a constructor that can pass initial data to the object.
|
||||
|
||||
## Registering components
|
||||
|
||||
A component can be registered like this:
|
||||
```ts
|
||||
import { ExampleComponent } from './ExampleComponent';
|
||||
import { component } from '@leanadmin/alpine-typescript';
|
||||
|
||||
component('example', ExampleComponent);
|
||||
```
|
||||
|
||||
Which will make it accessible using `Alpine.component('example')('foo', 'bar)`.
|
||||
|
||||
**Note: It's better to avoid using `Alpine.component('example', ExampleComponent)`** even if it might work in some cases. The reason for this is that `window.Alpine` might not yet be accessible when you're registering components, and if it is, it's possible that it's already evaluated some of the `x-data` attributes.
|
||||
|
||||
To register multiple components, you can use the `registerComponents()` helper.
|
||||
|
||||
This can pair well with scripts that crawl your e.g. `alpine/` directory to register all components using their file names.
|
||||
|
||||
```ts
|
||||
import { registerComponents } from '@leanadmin/alpine-typescript';
|
||||
|
||||
const files = require.context('./', true, /.*.ts/)
|
||||
.keys()
|
||||
.map(file => file.substr(2, file.length - 5)) // Remove ./ and .ts
|
||||
.filter(file => file !== 'index')
|
||||
.reduce((files: { [name: string]: Function }, file: string) => {
|
||||
files[file] = require(`./${file}.ts`).default;
|
||||
|
||||
return files;
|
||||
}, {});
|
||||
|
||||
registerComponents(files);
|
||||
```
|
||||
|
||||
## Class components
|
||||
|
||||
You can create class components by extending `AlpineComponent` and exporting the class as `default`.
|
||||
|
||||
The `AlpineComponent` provides IDE support for Alpine's magic properties. This means that you can use `this.$el`, `this.$nextTick(() => this.foo = this.bar)`, and more with full type support.
|
||||
|
||||
```ts
|
||||
import { AlpineComponent } from '@leanadmin/alpine-typescript';
|
||||
|
||||
export default class DarkModeToggle extends AlpineComponent {
|
||||
public theme: string|null = null;
|
||||
|
||||
/** Used for determining the transition direction. */
|
||||
public previousTheme: string|null = null;
|
||||
|
||||
public browserTheme(): string {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
public switchTheme(theme: string): void {
|
||||
this.$nextTick(() => this.previousTheme = this.theme);
|
||||
|
||||
this.theme = theme;
|
||||
|
||||
window.localStorage.setItem('leanTheme', theme);
|
||||
|
||||
this.updateDocumentClass(theme);
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
public init(): void {
|
||||
this.loadStoredTheme();
|
||||
this.registerListener();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Plain object components
|
||||
|
||||
To register a plain object as an Alpine component, return a function that wraps the object like this:
|
||||
```ts
|
||||
export default (foo: string, bar: number) => ({
|
||||
foo,
|
||||
bar,
|
||||
|
||||
someFunction() {
|
||||
console.log(this.foo);
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
The function will serve as a "constructor" for the object, setting default values and anything else you might need.
|
||||
|
||||
Note that the `=> ({` part is just syntactic sugar, you're free to use `return` if it's useful in your case:
|
||||
|
||||
```ts
|
||||
export default (foo: string, bar: number) => {
|
||||
return {
|
||||
foo,
|
||||
bar,
|
||||
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
28
package.json
Normal file
28
package.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "@leanadmin/alpine-typescript",
|
||||
"version": "0.1.0",
|
||||
"description": "TypeScript support for Alpine.js",
|
||||
"main": "src/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/LeanAdmin/alpine-typescript.git"
|
||||
},
|
||||
"files": [
|
||||
"src/index.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "npx mix --production",
|
||||
"prepublishOnly": "npm run-script build"
|
||||
},
|
||||
"keywords": [
|
||||
"alpine",
|
||||
"alpine.js",
|
||||
"typescript"
|
||||
],
|
||||
"author": "Samuel Štancl <samuel.stancl@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/LeanAdmin/alpine-typescript/issues"
|
||||
},
|
||||
"homepage": "https://github.com/LeanAdmin/alpine-typescript#readme"
|
||||
}
|
||||
81
src/index.ts
Normal file
81
src/index.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
declare global {
|
||||
interface Window {
|
||||
AlpineComponents: { [name: string]: ComponentConstructor };
|
||||
Alpine: any;
|
||||
deferLoadingAlpine: Function;
|
||||
}
|
||||
}
|
||||
|
||||
type ComponentConstructor = (...args: any[]) => object;
|
||||
|
||||
export class AlpineComponent {
|
||||
/** Retrieve the root component DOM node. */
|
||||
$el?: Element;
|
||||
|
||||
/** Retrieve DOM elements marked with x-ref inside the component. */
|
||||
$refs?: { [name: string]: Element };
|
||||
|
||||
/** Retrieve the native browser "Event" object within an event listener. */
|
||||
$event?: Event;
|
||||
|
||||
/** Create a CustomEvent and dispatch it using .dispatchEvent() internally. */
|
||||
$dispatch?: (event: string, data: object) => void;
|
||||
|
||||
/** Execute a given expression AFTER Alpine has made its reactive DOM updates. */
|
||||
$nextTick?: (callback: () => void) => void;
|
||||
|
||||
/** Will fire a provided callback when a component property you "watched" gets changed. */
|
||||
$watch?: (property: string, callback: (value: any) => void) => void;
|
||||
}
|
||||
|
||||
export function registerComponents(components: { [name: string]: Function }): { [name: string]: ComponentConstructor } {
|
||||
Object.entries(components).forEach(([name, file]) => {
|
||||
component(name, file);
|
||||
});
|
||||
|
||||
return window.AlpineComponents;
|
||||
}
|
||||
|
||||
export function component(name: string, component: Function = null): ComponentConstructor {
|
||||
if (! component) {
|
||||
return window.AlpineComponents[name];
|
||||
}
|
||||
|
||||
if (component['prototype'] instanceof AlpineComponent) {
|
||||
component = convertClassToAlpineConstructor(component);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
window.AlpineComponents[name] = component;
|
||||
}
|
||||
|
||||
export function convertClassToAlpineConstructor(component: any): ComponentConstructor {
|
||||
return function (...args: any[]) {
|
||||
let instance: AlpineComponent = new component(...args);
|
||||
|
||||
// Copy methods
|
||||
const methods = Object.getOwnPropertyNames(
|
||||
Object.getPrototypeOf(instance)
|
||||
)
|
||||
.reduce((obj, method) => {
|
||||
obj[method] = instance[method];
|
||||
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
// Copy properties
|
||||
return Object.assign(methods, instance);
|
||||
}
|
||||
}
|
||||
|
||||
export default () => {
|
||||
window.AlpineComponents = {};
|
||||
|
||||
const deferrer = window.deferLoadingAlpine || function (callback: CallableFunction) { callback() };
|
||||
|
||||
window.deferLoadingAlpine = function (callback: Function) {
|
||||
window.Alpine.component = component;
|
||||
|
||||
deferrer(callback);
|
||||
}
|
||||
}
|
||||
8
tsconfig.json
Normal file
8
tsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"lib": ["ES2017", "DOM"]
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue