зеркало из https://github.com/github/catalyst.git
biiiiig changes
This commit is contained in:
Родитель
27ea6d3cfb
Коммит
9d19ccd480
11
index.html
11
index.html
|
@ -5,13 +5,18 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<hello-controller>
|
<hello-controller>
|
||||||
<input data-target="HelloController.nameTarget" type="text" />
|
<input data-target="hello-controller.nameTarget" type="text" data-action="click:hello-controller#greet"/>
|
||||||
|
|
||||||
<button data-action="click->HelloController#greet">
|
<ajax-form>
|
||||||
|
|
||||||
|
<button data-action="click:hello-controller#greet">
|
||||||
Greet
|
Greet
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<span data-target="HelloController.outputTarget"> </span>
|
<span data-target="hello-controller.outputTarget"> </span>
|
||||||
|
|
||||||
|
</ajax-form>
|
||||||
|
|
||||||
</hello-controller>
|
</hello-controller>
|
||||||
<script src="dist/index.js"></script>
|
<script src="dist/index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import {dasherize} from './dasherize'
|
||||||
|
import {wrap} from './wrap'
|
||||||
|
import {getMethods} from './getmethods'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind `[data-action]` elements from the DOM to their actions.
|
||||||
|
*/
|
||||||
|
export function bindEvents(classObject: any) {
|
||||||
|
const methods = getMethods(classObject.prototype)
|
||||||
|
const name = dasherize(classObject.name)
|
||||||
|
wrap(classObject.prototype, 'connectedCallback', function (this: HTMLElement) {
|
||||||
|
const selectors = methods.map(method => `[data-action*=":${name}#${method}"]`).join(',')
|
||||||
|
for(const el of this.querySelectorAll(selectors)) {
|
||||||
|
// Match the pattern of `eventName:constructor#method`.
|
||||||
|
for(const binding of (el.getAttribute('data-action')||'').split(' ')) {
|
||||||
|
const [rest, method] = binding.split('#')
|
||||||
|
const [eventName, handler] = rest.split(':')
|
||||||
|
if (handler === name) {
|
||||||
|
el.addEventListener(eventName, (event: Event) => {
|
||||||
|
if (event.target === el) (this as any)[method](event)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export const dasherize = (str: string) => str.replace(/([a-zA-Z])(?=[A-Z])/g, '$1-').toLowerCase()
|
|
@ -0,0 +1,11 @@
|
||||||
|
export const getMethods = (obj: any) => {
|
||||||
|
let methods = []
|
||||||
|
for (const name of Object.getOwnPropertyNames(obj)) {
|
||||||
|
if (name === 'constructor') continue
|
||||||
|
const descriptor = Object.getOwnPropertyDescriptor(obj, name)
|
||||||
|
if (descriptor && descriptor.value && typeof descriptor.value === 'function') {
|
||||||
|
methods.push(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return methods
|
||||||
|
}
|
|
@ -1,51 +1,3 @@
|
||||||
function *bindings(selector: string, callingCtor: string) {
|
export {bindEvents} from './bind'
|
||||||
for(const binding of selector.split(' ')) {
|
export {register} from './register'
|
||||||
const [_, eventName, ref, ctor, method] = binding.match(/(?:(\w+)((?:@)\w+)?->)(\w+)#(\w+)/) || []
|
export {target, targets, assertTargets} from './target'
|
||||||
if (ctor === callingCtor) yield [eventName, ref, method]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function bind(classObject: any) {
|
|
||||||
const oldConnectedCallback = classObject.prototype.connectedCallback
|
|
||||||
classObject.prototype.connectedCallback = function () {
|
|
||||||
if (oldConnectedCallback) oldConnectedCallback.call(this)
|
|
||||||
for(const el of this.querySelectorAll('[data-action]')) {
|
|
||||||
for (const [eventName, ref, method] of bindings(el.getAttribute('data-action')||'', classObject.name)) {
|
|
||||||
let receiver = this
|
|
||||||
let delegate = el
|
|
||||||
if (ref === 'window') {
|
|
||||||
receiver = delegate = window
|
|
||||||
} else if (ref === 'document') {
|
|
||||||
receiver = delegate = document
|
|
||||||
}
|
|
||||||
receiver.addEventListener(eventName, (event: Event) => {
|
|
||||||
if (event.target === delegate) this[method](event)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function register(classObject: any) {
|
|
||||||
const name = classObject.name.replace(/([a-zA-Z])(?=[A-Z])/g, '$1-').toLowerCase()
|
|
||||||
if (!window.customElements.get(name)) {
|
|
||||||
window[classObject.name] = classObject;
|
|
||||||
window.customElements.define(name, classObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function target(proto: any, propertyKey: string) {
|
|
||||||
Object.defineProperty(proto, propertyKey, {
|
|
||||||
configurable: true,
|
|
||||||
get: function() {
|
|
||||||
const target = this.querySelector(
|
|
||||||
`[data-target=*"${this.constructor.name + "." + propertyKey}"]`
|
|
||||||
);
|
|
||||||
Object.defineProperty(this, propertyKey, {
|
|
||||||
value: target,
|
|
||||||
writable: true
|
|
||||||
});
|
|
||||||
return target;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
import {bind, register, target} from './helpers'
|
import {bindEvents, register, target, assertTargets} from './helpers'
|
||||||
|
|
||||||
@register
|
@register
|
||||||
@bind
|
@assertTargets
|
||||||
|
@bindEvents
|
||||||
class HelloController extends HTMLElement {
|
class HelloController extends HTMLElement {
|
||||||
@target outputTarget!: HTMLElement;
|
@target outputTarget!: HTMLElement;
|
||||||
@target nameTarget!: HTMLInputElement;
|
@target nameTarget!: HTMLInputElement;
|
||||||
|
@target buttonTarget!: HTMLButtonElement;
|
||||||
|
|
||||||
greet() {
|
greet() {
|
||||||
|
this.dataset.foo = 'foo'
|
||||||
this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!`;
|
this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import {dasherize} from './dasherize'
|
||||||
|
/**
|
||||||
|
* Register the controller as a custom element.
|
||||||
|
*
|
||||||
|
* The classname is converted to a approriate tag name.
|
||||||
|
*
|
||||||
|
* Example: HelloController => hello-controller
|
||||||
|
*/
|
||||||
|
export function register(classObject: any) {
|
||||||
|
const name = dasherize(classObject.name)
|
||||||
|
if (!window.customElements.get(name)) {
|
||||||
|
window[classObject.name] = classObject;
|
||||||
|
window.customElements.define(name, classObject);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import {dasherize} from './dasherize'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a property on the controller instance referencing the element with a
|
||||||
|
* `data-target` element of the same name.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const createSelector = (receiver: Element, key: string) => `[data-target~="${dasherize(receiver.constructor.name) + "." + key}"]`
|
||||||
|
|
||||||
|
export function target(proto: object, key: string) {
|
||||||
|
Object.defineProperty(
|
||||||
|
proto,
|
||||||
|
key,
|
||||||
|
{
|
||||||
|
configurable: true,
|
||||||
|
get: function() {
|
||||||
|
return this.querySelector(createSelector(this, key))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function targets(proto: object, key: string) {
|
||||||
|
Object.defineProperty(
|
||||||
|
proto,
|
||||||
|
key,
|
||||||
|
{
|
||||||
|
configurable: true,
|
||||||
|
get: function() {
|
||||||
|
return this.querySelectorAll(createSelector(this, key))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assertTargets(_: object) {
|
||||||
|
// This is an empty stub that does nothing at runtime, but can be used by
|
||||||
|
// compilers to generate code that asserts Targets are the Element types they
|
||||||
|
// declare themselves to be, otherwise throw an Invariant Error.
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
export const wrap = (obj: any, name: string, fn: (...args: any[]) => any) => {
|
||||||
|
if (!obj[name]) {
|
||||||
|
obj[name] = fn
|
||||||
|
} else {
|
||||||
|
const oldFn = obj[name]
|
||||||
|
obj[name] = function () {
|
||||||
|
oldFn.call(this)
|
||||||
|
fn.call(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,8 @@
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"lib": [
|
"lib": [
|
||||||
"es6",
|
"es6",
|
||||||
"dom"
|
"dom",
|
||||||
|
"dom.iterable"
|
||||||
],
|
],
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
|
|
Загрузка…
Ссылка в новой задаче