@pechynho/stimulus-typescript 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/README.md +275 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/portal-controller.d.ts +54 -0
- package/dist/portal-controller.js +792 -0
- package/dist/portal.d.ts +13 -0
- package/dist/portal.js +101 -0
- package/dist/resolvable.d.ts +28 -0
- package/dist/resolvable.js +54 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +56 -0
- package/dist/typed-stimulus.d.ts +78 -0
- package/dist/typed-stimulus.js +139 -0
- package/dist/typed.d.ts +69 -0
- package/dist/typed.js +60 -0
- package/dist/utils.d.ts +6 -0
- package/dist/utils.js +38 -0
- package/package.json +40 -0
- package/src/index.ts +6 -0
- package/src/portal-controller.ts +821 -0
- package/src/portal.ts +110 -0
- package/src/resolvable.ts +65 -0
- package/src/test.ts +72 -0
- package/src/typed.ts +178 -0
- package/src/utils.ts +41 -0
package/dist/portal.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
import Portal from "./portal-controller";
|
|
3
|
+
type Constructor<T = {}> = new (...args: any[]) => T;
|
|
4
|
+
export declare function Portals<Base extends Constructor<Controller>>(Base: Base): typeof Base & {
|
|
5
|
+
new (...args: any[]): InstanceType<Base> & {
|
|
6
|
+
readonly portalOutlet: Portal;
|
|
7
|
+
readonly hasPortalOutlet: boolean;
|
|
8
|
+
readonly portalOutlets: Portal[];
|
|
9
|
+
portalSelectorsValue: string[];
|
|
10
|
+
readonly hasPortalSelectorsValue: boolean;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export {};
|
package/dist/portal.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
|
|
2
|
+
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
|
3
|
+
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
|
4
|
+
};
|
|
5
|
+
export function Portals(Base) {
|
|
6
|
+
var _a;
|
|
7
|
+
let outlets = Base.outlets;
|
|
8
|
+
if (typeof outlets === 'undefined') {
|
|
9
|
+
outlets = [];
|
|
10
|
+
}
|
|
11
|
+
else if (!Array.isArray(outlets)) {
|
|
12
|
+
throw new Error('Outlets must be an array');
|
|
13
|
+
}
|
|
14
|
+
if (!outlets.includes('portal')) {
|
|
15
|
+
outlets.push('portal');
|
|
16
|
+
}
|
|
17
|
+
let values = Base.values;
|
|
18
|
+
if (typeof values === 'undefined') {
|
|
19
|
+
values = {};
|
|
20
|
+
}
|
|
21
|
+
else if (typeof values !== 'object') {
|
|
22
|
+
throw new Error('Values must be an object');
|
|
23
|
+
}
|
|
24
|
+
if (typeof values['portalSelectors'] === 'undefined') {
|
|
25
|
+
values['portalSelectors'] = {
|
|
26
|
+
type: Array,
|
|
27
|
+
default: [],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const derived = (_a = class extends Base {
|
|
31
|
+
constructor(...args) {
|
|
32
|
+
super(...args);
|
|
33
|
+
const portalOutlets = new Set();
|
|
34
|
+
const originalDisconnect = this.disconnect;
|
|
35
|
+
this.disconnect = function () {
|
|
36
|
+
if (portalOutlets.size > 0) {
|
|
37
|
+
for (const outlet of portalOutlets) {
|
|
38
|
+
outlet.unsync(this);
|
|
39
|
+
}
|
|
40
|
+
portalOutlets.clear();
|
|
41
|
+
}
|
|
42
|
+
if (typeof originalDisconnect === 'function') {
|
|
43
|
+
originalDisconnect.call(this);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const originalPortalOutletConnected = this.portalOutletConnected;
|
|
47
|
+
this.portalOutletConnected = function (outlet, element) {
|
|
48
|
+
outlet.sync(this);
|
|
49
|
+
portalOutlets.add(outlet);
|
|
50
|
+
if (typeof originalPortalOutletConnected === 'function') {
|
|
51
|
+
originalPortalOutletConnected.call(this, outlet, element);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const originalPortalOutletDisconnected = this.portalOutletDisconnected;
|
|
55
|
+
this.portalOutletDisconnected = function (outlet, element) {
|
|
56
|
+
outlet.unsync(this);
|
|
57
|
+
portalOutlets.delete(outlet);
|
|
58
|
+
if (typeof originalPortalOutletDisconnected === 'function') {
|
|
59
|
+
originalPortalOutletDisconnected.call(this, outlet, element);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const originalPortalSelectorsValueChanged = this.portalSelectorsValueChanged;
|
|
63
|
+
this.portalSelectorsValueChanged = function (value, previousValue) {
|
|
64
|
+
const outletAttribute = `data-${this.identifier}-portal-outlet`;
|
|
65
|
+
if (value.length > 0) {
|
|
66
|
+
const controllerAttribute = this.context.schema.controllerAttribute;
|
|
67
|
+
const selector = value.join(', ');
|
|
68
|
+
const portalElements = document.querySelectorAll(selector);
|
|
69
|
+
for (const portalElement of portalElements) {
|
|
70
|
+
if (!portalElement.hasAttribute(controllerAttribute)) {
|
|
71
|
+
portalElement.setAttribute(controllerAttribute, 'portal');
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const existingControllers = portalElement.getAttribute(controllerAttribute).split(' ');
|
|
75
|
+
if (!existingControllers.includes('portal')) {
|
|
76
|
+
existingControllers.push('portal');
|
|
77
|
+
portalElement.setAttribute(controllerAttribute, existingControllers.join(' '));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!this.element.hasAttribute(outletAttribute)) {
|
|
81
|
+
this.element.setAttribute(outletAttribute, selector);
|
|
82
|
+
}
|
|
83
|
+
else if (this.element.getAttribute(outletAttribute) !== selector) {
|
|
84
|
+
this.element.setAttribute(outletAttribute, selector);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (this.element.hasAttribute(outletAttribute)) {
|
|
88
|
+
this.element.removeAttribute(outletAttribute);
|
|
89
|
+
}
|
|
90
|
+
if (typeof originalPortalSelectorsValueChanged === 'function') {
|
|
91
|
+
originalPortalSelectorsValueChanged.call(this, value, previousValue);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
__setFunctionName(_a, "derived"),
|
|
97
|
+
_a.outlets = outlets,
|
|
98
|
+
_a.values = values,
|
|
99
|
+
_a);
|
|
100
|
+
return derived;
|
|
101
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Application, Controller } from "@hotwired/stimulus";
|
|
2
|
+
type Constructor<T = {}> = new (...args: any[]) => T;
|
|
3
|
+
export declare function Resolvable<Base extends Constructor<Controller>>(Base: Base, identifier: string): {
|
|
4
|
+
new (...args: any[]): {
|
|
5
|
+
readonly context: import("@hotwired/stimulus").Context;
|
|
6
|
+
get application(): Application;
|
|
7
|
+
get scope(): import("@hotwired/stimulus/dist/types/core/scope").Scope;
|
|
8
|
+
get element(): Element;
|
|
9
|
+
get identifier(): string;
|
|
10
|
+
get targets(): import("@hotwired/stimulus/dist/types/core/target_set").TargetSet;
|
|
11
|
+
get outlets(): import("@hotwired/stimulus/dist/types/core/outlet_set").OutletSet;
|
|
12
|
+
get classes(): import("@hotwired/stimulus/dist/types/core/class_map").ClassMap;
|
|
13
|
+
get data(): import("@hotwired/stimulus/dist/types/core/data_map").DataMap;
|
|
14
|
+
initialize(): void;
|
|
15
|
+
connect(): void;
|
|
16
|
+
disconnect(): void;
|
|
17
|
+
dispatch(eventName: string, { target, detail, prefix, bubbles, cancelable, }?: Partial<{
|
|
18
|
+
target: Element | Window | Document;
|
|
19
|
+
detail: Object;
|
|
20
|
+
prefix: string;
|
|
21
|
+
bubbles: boolean;
|
|
22
|
+
cancelable: boolean;
|
|
23
|
+
}>): CustomEvent<Object>;
|
|
24
|
+
};
|
|
25
|
+
get<T extends Constructor<Controller>>(this: T, element: HTMLElement): InstanceType<T> | null;
|
|
26
|
+
getAsync<T extends Constructor<Controller>>(this: T, element: HTMLElement, timeout?: number, poll?: number): Promise<InstanceType<T> | null>;
|
|
27
|
+
} & Base;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { getController, getControllerAsync } from "./utils";
|
|
2
|
+
const identifierToAppMap = new Map();
|
|
3
|
+
export function Resolvable(Base, identifier) {
|
|
4
|
+
return class extends Base {
|
|
5
|
+
constructor(...args) {
|
|
6
|
+
super(...args);
|
|
7
|
+
const originalConnect = this.connect;
|
|
8
|
+
this.connect = function () {
|
|
9
|
+
identifierToAppMap.set(this.identifier, this.application);
|
|
10
|
+
if (typeof originalConnect === 'function') {
|
|
11
|
+
originalConnect.call(this);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
static get(element) {
|
|
16
|
+
const app = identifierToAppMap.get(identifier);
|
|
17
|
+
if (typeof app === 'undefined') {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return getController(app, element, identifier);
|
|
21
|
+
}
|
|
22
|
+
static getAsync(element, timeout = 5000, poll = 50) {
|
|
23
|
+
const app = identifierToAppMap.get(identifier);
|
|
24
|
+
if (typeof app !== 'undefined') {
|
|
25
|
+
return getControllerAsync(app, element, identifier, timeout, poll);
|
|
26
|
+
}
|
|
27
|
+
const startTime = Date.now();
|
|
28
|
+
const maxAttempts = 10;
|
|
29
|
+
let attempts = 0;
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
const checkApp = async () => {
|
|
32
|
+
attempts++;
|
|
33
|
+
const app = identifierToAppMap.get(identifier);
|
|
34
|
+
if (typeof app !== 'undefined') {
|
|
35
|
+
const remainingTime = timeout - (Date.now() - startTime);
|
|
36
|
+
remainingTime <= 0
|
|
37
|
+
? resolve(getController(app, element, identifier))
|
|
38
|
+
: resolve(await getControllerAsync(app, element, identifier, remainingTime, poll));
|
|
39
|
+
}
|
|
40
|
+
else if (Date.now() - startTime >= timeout) {
|
|
41
|
+
resolve(null);
|
|
42
|
+
}
|
|
43
|
+
else if (attempts <= maxAttempts) {
|
|
44
|
+
setTimeout(checkApp);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
setTimeout(checkApp, poll);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
checkApp().catch(error => console.error(error));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
package/dist/test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/test.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
|
+
import { Target, Typed, TypedArray, TypedObject } from './index';
|
|
3
|
+
import { Portals } from "./portal";
|
|
4
|
+
import { Resolvable } from "./resolvable";
|
|
5
|
+
class OutletController extends Typed(Controller) {
|
|
6
|
+
method() {
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
class HomepageController extends Typed(Portals(Resolvable((Controller), 'homepage')), {
|
|
10
|
+
values: {
|
|
11
|
+
name: String,
|
|
12
|
+
counter: Number,
|
|
13
|
+
isActive: Boolean,
|
|
14
|
+
alias: TypedArray(),
|
|
15
|
+
address: TypedObject(),
|
|
16
|
+
metadata: { type: TypedObject() },
|
|
17
|
+
},
|
|
18
|
+
targets: {
|
|
19
|
+
form: HTMLFormElement,
|
|
20
|
+
select: HTMLSelectElement,
|
|
21
|
+
custom: Target(),
|
|
22
|
+
},
|
|
23
|
+
classes: ['selected', 'highlighted'],
|
|
24
|
+
outlets: {
|
|
25
|
+
'test': OutletController,
|
|
26
|
+
},
|
|
27
|
+
}) {
|
|
28
|
+
// All properties are now strongly typed!
|
|
29
|
+
connect() {
|
|
30
|
+
// String values
|
|
31
|
+
this.nameValue.split(' ');
|
|
32
|
+
// Number values
|
|
33
|
+
Math.floor(this.counterValue);
|
|
34
|
+
// Boolean values
|
|
35
|
+
this.isActiveValue;
|
|
36
|
+
// Array values
|
|
37
|
+
this.aliasValue.map(alias => alias.toUpperCase());
|
|
38
|
+
// Object values
|
|
39
|
+
console.log(this.addressValue.street);
|
|
40
|
+
console.log(this.metadataValue.title);
|
|
41
|
+
// Targets
|
|
42
|
+
this.formTarget.submit();
|
|
43
|
+
this.selectTarget.value = 'stimulus';
|
|
44
|
+
this.customTarget.someCustomMethod();
|
|
45
|
+
// Outlets
|
|
46
|
+
this.testOutlet.method();
|
|
47
|
+
// Classes
|
|
48
|
+
if (this.hasSelectedClass) {
|
|
49
|
+
console.log(this.selectedClass);
|
|
50
|
+
}
|
|
51
|
+
// Portals
|
|
52
|
+
if (this.hasPortalOutlet) {
|
|
53
|
+
console.log(this.portalOutlet);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
import Portal from "./portal-controller";
|
|
3
|
+
declare class Wrapped<T = any> {
|
|
4
|
+
readonly context: 'typed-object' | 'typed-array' | 'target';
|
|
5
|
+
private _;
|
|
6
|
+
constructor(context: 'typed-object' | 'typed-array' | 'target');
|
|
7
|
+
}
|
|
8
|
+
export declare const TypedObject: <T extends object>() => Wrapped<T>;
|
|
9
|
+
export declare const TypedArray: <T>() => Wrapped<T[]>;
|
|
10
|
+
export declare const Target: <T extends object>() => Wrapped<T>;
|
|
11
|
+
type Constructor<T = {}> = new (...args: any[]) => T;
|
|
12
|
+
type CamelCase<K extends string> = K extends `${infer T}_${infer U}` ? `${Uncapitalize<T>}${Capitalize<CamelCase<U>>}` : K extends `${infer T}-${infer U}` ? `${Uncapitalize<T>}${Capitalize<CamelCase<U>>}` : K extends `${infer T} ${infer U}` ? `${Uncapitalize<T>}${Capitalize<CamelCase<U>>}` : K;
|
|
13
|
+
type ClassProperties<Classes extends readonly string[] = []> = {
|
|
14
|
+
[K in Classes[number] as `${CamelCase<K>}Class`]: string;
|
|
15
|
+
} & {
|
|
16
|
+
readonly [K in Classes[number] as `has${Capitalize<CamelCase<K>>}Class`]: boolean;
|
|
17
|
+
} & {
|
|
18
|
+
[K in Classes[number] as `${CamelCase<K>}Classes`]: string[];
|
|
19
|
+
};
|
|
20
|
+
type ValueTypeDefault = string | number | boolean | Array<any> | Object | InstanceType<typeof Wrapped>;
|
|
21
|
+
type ValueTypeConstant = typeof String | typeof Number | typeof Boolean | typeof Array<any> | typeof Object | InstanceType<typeof Wrapped>;
|
|
22
|
+
type ValueTypeObject = {
|
|
23
|
+
type: ValueTypeConstant;
|
|
24
|
+
default?: ValueTypeDefault;
|
|
25
|
+
};
|
|
26
|
+
type ValueTypeDefinition = ValueTypeConstant | ValueTypeObject | InstanceType<typeof Wrapped>;
|
|
27
|
+
type ValueDefinitionMap = {
|
|
28
|
+
[token: string]: ValueTypeDefinition;
|
|
29
|
+
};
|
|
30
|
+
type TypeFromConstructor<C> = C extends StringConstructor ? string : C extends NumberConstructor ? number : C extends BooleanConstructor ? boolean : C extends ArrayConstructor ? any[] : C extends Wrapped<infer T> ? T : C extends ObjectConstructor ? Object : C extends Constructor<infer T> ? TypeFromConstructor<T> : never;
|
|
31
|
+
type TransformValueDefinition<T extends ValueTypeDefinition> = T extends {
|
|
32
|
+
type: infer U;
|
|
33
|
+
} ? TypeFromConstructor<U> : TypeFromConstructor<T>;
|
|
34
|
+
type ValuesProperties<Values extends ValueDefinitionMap> = {
|
|
35
|
+
[K in keyof Values as `${CamelCase<K & string>}Value`]: TransformValueDefinition<Values[K]>;
|
|
36
|
+
} & {
|
|
37
|
+
readonly [K in keyof Values as `has${Capitalize<CamelCase<K & string>>}Value`]: boolean;
|
|
38
|
+
};
|
|
39
|
+
type TargetTypeDefinition = typeof Element | InstanceType<typeof Wrapped>;
|
|
40
|
+
type TargetsDefinitionMap = {
|
|
41
|
+
[token: string]: TargetTypeDefinition;
|
|
42
|
+
};
|
|
43
|
+
type TransformTargetDefinition<T extends TargetTypeDefinition> = T extends Wrapped<infer U> ? U : T extends new (...args: any[]) => infer R ? R : never;
|
|
44
|
+
type TargetsProperties<Targets extends TargetsDefinitionMap> = {
|
|
45
|
+
readonly [K in keyof Targets as `${CamelCase<K & string>}Target`]: TransformTargetDefinition<Targets[K]>;
|
|
46
|
+
} & {
|
|
47
|
+
readonly [K in keyof Targets as `has${Capitalize<CamelCase<K & string>>}Target`]: boolean;
|
|
48
|
+
} & {
|
|
49
|
+
readonly [K in keyof Targets as `${CamelCase<K & string>}Targets`]: TransformTargetDefinition<Targets[K]>[];
|
|
50
|
+
};
|
|
51
|
+
type OutletsDefinitionMap = {
|
|
52
|
+
[token: string]: Constructor<Controller>;
|
|
53
|
+
};
|
|
54
|
+
type OutletProperties<Outlets extends OutletsDefinitionMap> = {
|
|
55
|
+
readonly [K in keyof Outlets as `${CamelCase<K & string>}Outlet`]: InstanceType<Outlets[K]>;
|
|
56
|
+
} & {
|
|
57
|
+
readonly [K in keyof Outlets as `has${Capitalize<CamelCase<K & string>>}Outlet`]: boolean;
|
|
58
|
+
} & {
|
|
59
|
+
readonly [K in keyof Outlets as `${CamelCase<K & string>}Outlets`]: InstanceType<Outlets[K]>[];
|
|
60
|
+
};
|
|
61
|
+
type PortalProperties<Portals extends true | undefined> = Portals extends boolean ? {
|
|
62
|
+
readonly portalOutlet: Portal;
|
|
63
|
+
readonly hasPortalOutlet: boolean;
|
|
64
|
+
readonly portalOutlets: Portal[];
|
|
65
|
+
portalSelectorsValue: string[];
|
|
66
|
+
readonly hasPortalSelectorsValue: boolean;
|
|
67
|
+
} : {};
|
|
68
|
+
type Configuration<Values extends ValueDefinitionMap, Targets extends TargetsDefinitionMap, Classes extends readonly string[], Outlets extends OutletsDefinitionMap, Portals extends true | undefined> = {
|
|
69
|
+
values?: Values;
|
|
70
|
+
targets?: Targets;
|
|
71
|
+
classes?: Classes;
|
|
72
|
+
outlets?: Outlets;
|
|
73
|
+
portals?: Portals;
|
|
74
|
+
};
|
|
75
|
+
export declare function Typed<Base extends Constructor<Controller>, Values extends ValueDefinitionMap = {}, Targets extends TargetsDefinitionMap = {}, Classes extends readonly string[] = [], Outlets extends OutletsDefinitionMap = {}, Portals extends true | undefined = undefined>(Base: Base, configuration?: Configuration<Values, Targets, Classes, Outlets, Portals>): typeof Base & {
|
|
76
|
+
new (...any: any[]): InstanceType<Base> & ValuesProperties<Values> & TargetsProperties<Targets> & ClassProperties<Classes> & OutletProperties<Outlets> & PortalProperties<Portals>;
|
|
77
|
+
};
|
|
78
|
+
export {};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
|
|
2
|
+
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
|
3
|
+
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
|
4
|
+
};
|
|
5
|
+
class Wrapped {
|
|
6
|
+
constructor(context) {
|
|
7
|
+
this.context = context;
|
|
8
|
+
this._ = undefined;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export const TypedObject = () => new Wrapped('typed-object');
|
|
12
|
+
export const TypedArray = () => new Wrapped('typed-array');
|
|
13
|
+
export const Target = () => new Wrapped('target');
|
|
14
|
+
function PortalsMixin(Base) {
|
|
15
|
+
return class extends Base {
|
|
16
|
+
constructor(...any) {
|
|
17
|
+
super(...any);
|
|
18
|
+
const portalOutlets = new Set();
|
|
19
|
+
const originalDisconnect = this.disconnect;
|
|
20
|
+
this.disconnect = function () {
|
|
21
|
+
if (portalOutlets.size > 0) {
|
|
22
|
+
for (const outlet of portalOutlets) {
|
|
23
|
+
outlet.unsync(this);
|
|
24
|
+
}
|
|
25
|
+
portalOutlets.clear();
|
|
26
|
+
}
|
|
27
|
+
if (typeof originalDisconnect === 'function') {
|
|
28
|
+
originalDisconnect.call(this);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const originalPortalOutletConnected = this.portalOutletConnected;
|
|
32
|
+
this.portalOutletConnected = function (outlet, element) {
|
|
33
|
+
outlet.sync(this);
|
|
34
|
+
portalOutlets.add(outlet);
|
|
35
|
+
if (typeof originalPortalOutletConnected === 'function') {
|
|
36
|
+
originalPortalOutletConnected.call(this, outlet, element);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const originalPortalOutletDisconnected = this.portalOutletDisconnected;
|
|
40
|
+
this.portalOutletDisconnected = function (outlet, element) {
|
|
41
|
+
outlet.unsync(this);
|
|
42
|
+
portalOutlets.delete(outlet);
|
|
43
|
+
if (typeof originalPortalOutletDisconnected === 'function') {
|
|
44
|
+
originalPortalOutletDisconnected.call(this, outlet, element);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const originalPortalSelectorsValueChanged = this.portalSelectorsValueChanged;
|
|
48
|
+
this.portalSelectorsValueChanged = function (value, previousValue) {
|
|
49
|
+
const outletAttribute = `data-${this.identifier}-portal-outlet`;
|
|
50
|
+
if (value.length > 0) {
|
|
51
|
+
const controllerAttribute = this.context.schema.controllerAttribute;
|
|
52
|
+
const selector = value.join(', ');
|
|
53
|
+
const portalElements = document.querySelectorAll(selector);
|
|
54
|
+
for (const portalElement of portalElements) {
|
|
55
|
+
if (!portalElement.hasAttribute(controllerAttribute)) {
|
|
56
|
+
portalElement.setAttribute(controllerAttribute, 'portal');
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const existingControllers = portalElement.getAttribute(controllerAttribute).split(' ');
|
|
60
|
+
if (!existingControllers.includes('portal')) {
|
|
61
|
+
existingControllers.push('portal');
|
|
62
|
+
portalElement.setAttribute(controllerAttribute, existingControllers.join(' '));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (!this.element.hasAttribute(outletAttribute)) {
|
|
66
|
+
this.element.setAttribute(outletAttribute, selector);
|
|
67
|
+
}
|
|
68
|
+
else if (this.element.getAttribute(outletAttribute) !== selector) {
|
|
69
|
+
this.element.setAttribute(outletAttribute, selector);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else if (this.element.hasAttribute(outletAttribute)) {
|
|
73
|
+
this.element.removeAttribute(outletAttribute);
|
|
74
|
+
}
|
|
75
|
+
if (typeof originalPortalSelectorsValueChanged === 'function') {
|
|
76
|
+
originalPortalSelectorsValueChanged.call(this, value, previousValue);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function patchValueTypeDefinitionMap(values) {
|
|
83
|
+
const patchedValues = {};
|
|
84
|
+
const patchType = (type) => {
|
|
85
|
+
if (type instanceof Wrapped && type.context === 'typed-object') {
|
|
86
|
+
return Object;
|
|
87
|
+
}
|
|
88
|
+
if (type instanceof Wrapped && type.context === 'typed-array') {
|
|
89
|
+
return Array;
|
|
90
|
+
}
|
|
91
|
+
return type;
|
|
92
|
+
};
|
|
93
|
+
Object.getOwnPropertyNames(values).forEach(key => {
|
|
94
|
+
const definition = values[key];
|
|
95
|
+
if (typeof definition === 'object' && 'default' in definition && 'type' in definition) {
|
|
96
|
+
patchedValues[key] = {
|
|
97
|
+
type: patchType(definition.type),
|
|
98
|
+
default: definition.default,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
else if (typeof definition === 'object' && 'type' in definition) {
|
|
102
|
+
patchedValues[key] = patchType(definition.type);
|
|
103
|
+
}
|
|
104
|
+
else if (definition instanceof Wrapped) {
|
|
105
|
+
patchedValues[key] = patchType(definition);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
patchedValues[key] = definition;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
return patchedValues;
|
|
112
|
+
}
|
|
113
|
+
export function Typed(Base, configuration) {
|
|
114
|
+
var _a;
|
|
115
|
+
const { values, targets, classes, outlets, portals } = configuration !== null && configuration !== void 0 ? configuration : {};
|
|
116
|
+
const patchedOutlet = Object.getOwnPropertyNames(outlets !== null && outlets !== void 0 ? outlets : {});
|
|
117
|
+
const patchedValues = patchValueTypeDefinitionMap(values !== null && values !== void 0 ? values : {});
|
|
118
|
+
if (portals === true) {
|
|
119
|
+
patchedOutlet.push('portal');
|
|
120
|
+
if (typeof patchedValues['portalSelectors'] === 'undefined') {
|
|
121
|
+
patchedValues['portalSelectors'] = { type: TypedArray(), default: [] };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
let derived = (_a = class extends Base {
|
|
125
|
+
constructor(...any) {
|
|
126
|
+
super(...any);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
__setFunctionName(_a, "derived"),
|
|
130
|
+
_a.values = patchedValues,
|
|
131
|
+
_a.targets = Object.getOwnPropertyNames(targets !== null && targets !== void 0 ? targets : {}),
|
|
132
|
+
_a.classes = classes !== null && classes !== void 0 ? classes : [],
|
|
133
|
+
_a.outlets = patchedOutlet,
|
|
134
|
+
_a);
|
|
135
|
+
if (portals === true) {
|
|
136
|
+
derived = PortalsMixin(derived);
|
|
137
|
+
}
|
|
138
|
+
return derived;
|
|
139
|
+
}
|
package/dist/typed.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
declare class Wrapped<T = any> {
|
|
3
|
+
readonly context: 'typed-object' | 'typed-array' | 'target';
|
|
4
|
+
private _;
|
|
5
|
+
constructor(context: 'typed-object' | 'typed-array' | 'target');
|
|
6
|
+
}
|
|
7
|
+
export declare const TypedObject: <T extends object>() => Wrapped<T>;
|
|
8
|
+
export declare const TypedArray: <T>() => Wrapped<T[]>;
|
|
9
|
+
export declare const Target: <T extends object>() => Wrapped<T>;
|
|
10
|
+
type Constructor<T = {}> = new (...args: any[]) => T;
|
|
11
|
+
type CamelCase<K extends string> = K extends `${infer T}_${infer U}` ? `${Uncapitalize<T>}${Capitalize<CamelCase<U>>}` : K extends `${infer T}-${infer U}` ? `${Uncapitalize<T>}${Capitalize<CamelCase<U>>}` : K extends `${infer T} ${infer U}` ? `${Uncapitalize<T>}${Capitalize<CamelCase<U>>}` : K;
|
|
12
|
+
type ClassProperties<Classes extends readonly string[] = []> = {
|
|
13
|
+
[K in Classes[number] as `${CamelCase<K>}Class`]: string;
|
|
14
|
+
} & {
|
|
15
|
+
readonly [K in Classes[number] as `has${Capitalize<CamelCase<K>>}Class`]: boolean;
|
|
16
|
+
} & {
|
|
17
|
+
[K in Classes[number] as `${CamelCase<K>}Classes`]: string[];
|
|
18
|
+
};
|
|
19
|
+
type ValueTypeDefault = string | number | boolean | Array<any> | Object | InstanceType<typeof Wrapped>;
|
|
20
|
+
type ValueTypeConstant = typeof String | typeof Number | typeof Boolean | typeof Array<any> | typeof Object | InstanceType<typeof Wrapped>;
|
|
21
|
+
type ValueTypeObject = {
|
|
22
|
+
type: ValueTypeConstant;
|
|
23
|
+
default?: ValueTypeDefault;
|
|
24
|
+
};
|
|
25
|
+
type ValueTypeDefinition = ValueTypeConstant | ValueTypeObject | InstanceType<typeof Wrapped>;
|
|
26
|
+
type ValueDefinitionMap = {
|
|
27
|
+
[token: string]: ValueTypeDefinition;
|
|
28
|
+
};
|
|
29
|
+
type TypeFromConstructor<C> = C extends StringConstructor ? string : C extends NumberConstructor ? number : C extends BooleanConstructor ? boolean : C extends ArrayConstructor ? any[] : C extends Wrapped<infer T> ? T : C extends ObjectConstructor ? Object : C extends Constructor<infer T> ? TypeFromConstructor<T> : never;
|
|
30
|
+
type TransformValueDefinition<T extends ValueTypeDefinition> = T extends {
|
|
31
|
+
type: infer U;
|
|
32
|
+
} ? TypeFromConstructor<U> : TypeFromConstructor<T>;
|
|
33
|
+
type ValuesProperties<Values extends ValueDefinitionMap> = {
|
|
34
|
+
[K in keyof Values as `${CamelCase<K & string>}Value`]: TransformValueDefinition<Values[K]>;
|
|
35
|
+
} & {
|
|
36
|
+
readonly [K in keyof Values as `has${Capitalize<CamelCase<K & string>>}Value`]: boolean;
|
|
37
|
+
};
|
|
38
|
+
type TargetTypeDefinition = typeof Element | InstanceType<typeof Wrapped>;
|
|
39
|
+
type TargetsDefinitionMap = {
|
|
40
|
+
[token: string]: TargetTypeDefinition;
|
|
41
|
+
};
|
|
42
|
+
type TransformTargetDefinition<T extends TargetTypeDefinition> = T extends Wrapped<infer U> ? U : T extends new (...args: any[]) => infer R ? R : never;
|
|
43
|
+
type TargetsProperties<Targets extends TargetsDefinitionMap> = {
|
|
44
|
+
readonly [K in keyof Targets as `${CamelCase<K & string>}Target`]: TransformTargetDefinition<Targets[K]>;
|
|
45
|
+
} & {
|
|
46
|
+
readonly [K in keyof Targets as `has${Capitalize<CamelCase<K & string>>}Target`]: boolean;
|
|
47
|
+
} & {
|
|
48
|
+
readonly [K in keyof Targets as `${CamelCase<K & string>}Targets`]: TransformTargetDefinition<Targets[K]>[];
|
|
49
|
+
};
|
|
50
|
+
type OutletsDefinitionMap = {
|
|
51
|
+
[token: string]: Constructor<Controller>;
|
|
52
|
+
};
|
|
53
|
+
type OutletProperties<Outlets extends OutletsDefinitionMap> = {
|
|
54
|
+
readonly [K in keyof Outlets as `${CamelCase<K & string>}Outlet`]: InstanceType<Outlets[K]>;
|
|
55
|
+
} & {
|
|
56
|
+
readonly [K in keyof Outlets as `has${Capitalize<CamelCase<K & string>>}Outlet`]: boolean;
|
|
57
|
+
} & {
|
|
58
|
+
readonly [K in keyof Outlets as `${CamelCase<K & string>}Outlets`]: InstanceType<Outlets[K]>[];
|
|
59
|
+
};
|
|
60
|
+
type Configuration<Values extends ValueDefinitionMap, Targets extends TargetsDefinitionMap, Classes extends readonly string[], Outlets extends OutletsDefinitionMap> = {
|
|
61
|
+
values?: Values;
|
|
62
|
+
targets?: Targets;
|
|
63
|
+
classes?: Classes;
|
|
64
|
+
outlets?: Outlets;
|
|
65
|
+
};
|
|
66
|
+
export declare function Typed<Base extends Constructor<Controller>, Values extends ValueDefinitionMap = {}, Targets extends TargetsDefinitionMap = {}, Classes extends readonly string[] = [], Outlets extends OutletsDefinitionMap = {}>(Base: Base, configuration?: Configuration<Values, Targets, Classes, Outlets>): typeof Base & {
|
|
67
|
+
new (...args: any[]): InstanceType<Base> & ValuesProperties<Values> & TargetsProperties<Targets> & ClassProperties<Classes> & OutletProperties<Outlets>;
|
|
68
|
+
};
|
|
69
|
+
export {};
|
package/dist/typed.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
|
|
2
|
+
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
|
3
|
+
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
|
4
|
+
};
|
|
5
|
+
class Wrapped {
|
|
6
|
+
constructor(context) {
|
|
7
|
+
this.context = context;
|
|
8
|
+
this._ = undefined;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export const TypedObject = () => new Wrapped('typed-object');
|
|
12
|
+
export const TypedArray = () => new Wrapped('typed-array');
|
|
13
|
+
export const Target = () => new Wrapped('target');
|
|
14
|
+
function patchValueTypeDefinitionMap(values) {
|
|
15
|
+
const patchedValues = {};
|
|
16
|
+
const patchType = (type) => {
|
|
17
|
+
if (type instanceof Wrapped && type.context === 'typed-object') {
|
|
18
|
+
return Object;
|
|
19
|
+
}
|
|
20
|
+
if (type instanceof Wrapped && type.context === 'typed-array') {
|
|
21
|
+
return Array;
|
|
22
|
+
}
|
|
23
|
+
return type;
|
|
24
|
+
};
|
|
25
|
+
Object.getOwnPropertyNames(values).forEach(key => {
|
|
26
|
+
const definition = values[key];
|
|
27
|
+
if (typeof definition === 'object' && 'default' in definition && 'type' in definition) {
|
|
28
|
+
patchedValues[key] = {
|
|
29
|
+
type: patchType(definition.type),
|
|
30
|
+
default: definition.default,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
else if (typeof definition === 'object' && 'type' in definition) {
|
|
34
|
+
patchedValues[key] = patchType(definition.type);
|
|
35
|
+
}
|
|
36
|
+
else if (definition instanceof Wrapped) {
|
|
37
|
+
patchedValues[key] = patchType(definition);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
patchedValues[key] = definition;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return patchedValues;
|
|
44
|
+
}
|
|
45
|
+
export function Typed(Base, configuration) {
|
|
46
|
+
var _a;
|
|
47
|
+
const { values, targets, classes, outlets } = configuration !== null && configuration !== void 0 ? configuration : {};
|
|
48
|
+
const derived = (_a = class extends Base {
|
|
49
|
+
constructor(...args) {
|
|
50
|
+
super(...args);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
__setFunctionName(_a, "derived"),
|
|
54
|
+
_a.values = patchValueTypeDefinitionMap(values !== null && values !== void 0 ? values : {}),
|
|
55
|
+
_a.targets = Object.getOwnPropertyNames(targets !== null && targets !== void 0 ? targets : {}),
|
|
56
|
+
_a.classes = classes !== null && classes !== void 0 ? classes : [],
|
|
57
|
+
_a.outlets = Object.getOwnPropertyNames(outlets !== null && outlets !== void 0 ? outlets : {}),
|
|
58
|
+
_a);
|
|
59
|
+
return derived;
|
|
60
|
+
}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ActionEvent, Application, Controller } from "@hotwired/stimulus";
|
|
2
|
+
export declare const camelCase: (value: string) => string;
|
|
3
|
+
export declare const capitalize: (value: string) => string;
|
|
4
|
+
export declare const isActionEvent: (value: any) => value is ActionEvent;
|
|
5
|
+
export declare const getController: <T extends Controller>(app: Application, element: HTMLElement, identifier: string) => T | null;
|
|
6
|
+
export declare const getControllerAsync: <T extends Controller>(app: Application, element: HTMLElement, identifier: string, timeout?: number, poll?: number) => Promise<T | null>;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const camelCase = (value) => {
|
|
2
|
+
return value
|
|
3
|
+
.replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
|
|
4
|
+
.replace(/^[A-Z]/, c => c.toLowerCase());
|
|
5
|
+
};
|
|
6
|
+
export const capitalize = (value) => {
|
|
7
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
8
|
+
};
|
|
9
|
+
export const isActionEvent = (value) => {
|
|
10
|
+
return value instanceof Event && 'params' in value && typeof value.params !== 'object';
|
|
11
|
+
};
|
|
12
|
+
export const getController = (app, element, identifier) => {
|
|
13
|
+
return app.getControllerForElementAndIdentifier(element, identifier);
|
|
14
|
+
};
|
|
15
|
+
export const getControllerAsync = async (app, element, identifier, timeout = 5000, poll = 50) => {
|
|
16
|
+
const startTime = Date.now();
|
|
17
|
+
const maxAttempts = 10;
|
|
18
|
+
let attempts = 0;
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
const checkController = () => {
|
|
21
|
+
attempts++;
|
|
22
|
+
const controller = app.getControllerForElementAndIdentifier(element, identifier);
|
|
23
|
+
if (controller !== null) {
|
|
24
|
+
resolve(controller);
|
|
25
|
+
}
|
|
26
|
+
else if (Date.now() - startTime >= timeout) {
|
|
27
|
+
resolve(null);
|
|
28
|
+
}
|
|
29
|
+
else if (attempts <= maxAttempts) {
|
|
30
|
+
setTimeout(checkController);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
setTimeout(checkController, poll);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
checkController();
|
|
37
|
+
});
|
|
38
|
+
};
|