зеркало из 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>
|
||||
<body>
|
||||
<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
|
||||
</button>
|
||||
|
||||
<span data-target="HelloController.outputTarget"> </span>
|
||||
<span data-target="hello-controller.outputTarget"> </span>
|
||||
|
||||
</ajax-form>
|
||||
|
||||
</hello-controller>
|
||||
<script src="dist/index.js"></script>
|
||||
</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) {
|
||||
for(const binding of selector.split(' ')) {
|
||||
const [_, eventName, ref, ctor, method] = binding.match(/(?:(\w+)((?:@)\w+)?->)(\w+)#(\w+)/) || []
|
||||
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;
|
||||
},
|
||||
});
|
||||
}
|
||||
export {bindEvents} from './bind'
|
||||
export {register} from './register'
|
||||
export {target, targets, assertTargets} from './target'
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import {bind, register, target} from './helpers'
|
||||
import {bindEvents, register, target, assertTargets} from './helpers'
|
||||
|
||||
@register
|
||||
@bind
|
||||
@assertTargets
|
||||
@bindEvents
|
||||
class HelloController extends HTMLElement {
|
||||
@target outputTarget!: HTMLElement;
|
||||
@target nameTarget!: HTMLInputElement;
|
||||
@target buttonTarget!: HTMLButtonElement;
|
||||
|
||||
greet() {
|
||||
this.dataset.foo = 'foo'
|
||||
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,
|
||||
"lib": [
|
||||
"es6",
|
||||
"dom"
|
||||
"dom",
|
||||
"dom.iterable"
|
||||
],
|
||||
"target": "ES2020",
|
||||
"rootDir": "src",
|
||||
|
|
Загрузка…
Ссылка в новой задаче