element-vir 2.0.4 → 4.0.0

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/README.md CHANGED
@@ -1,18 +1,19 @@
1
1
  # element-vir
2
2
 
3
- Heroic, reactive, functional, type safe, custom web components.
3
+ Heroic. Reactive. Functional. Type safe. Web components without compromise.
4
4
 
5
5
  No need for an extra build step,<br>
6
- no need for weird file extensions,<br>
6
+ no need for side effect imports, <br>
7
+ no need for unique file extensions,<br>
7
8
  no need for extra static analysis tools,<br>
8
- no need for a dedicated funky syntax.<br>
9
+ no need for a dedicated, unique syntax.<br>
9
10
  _**It's just TypeScript.**_
10
11
 
11
- Built using the power of _native_ JavaScript custom web elements, _native_ JavaScript template literals, _native_ JavaScript functions<sup>\*</sup>, _native_ HTML, and [lit-element](http://lit.dev).
12
+ Uses the power of _native_ JavaScript custom web elements, _native_ JavaScript template literals, _native_ JavaScript functions<sup>\*</sup>, _native_ HTML, and [lit-element](http://lit.dev).
12
13
 
13
- This is basically a [lit-element](http://lit.dev) wrapper that adds type-safe I/O and functional-programming style component definition.
14
+ In reality this is basically a [lit-element](http://lit.dev) wrapper that adds type-safe element tag usage and I/O with functional-programming style component definition.
14
15
 
15
- [Works in every major browser except Internet Explorer.](https://caniuse.com/mdn-api_window_customelements) <sub>(Heaven help you if you still need to support IE.)</sub>
16
+ [Works in every major web browser except Internet Explorer.](https://caniuse.com/mdn-api_window_customelements)
16
17
 
17
18
  <sub>\*okay I hope it's obvious that functions are native</sub>
18
19
 
@@ -24,15 +25,17 @@ This is basically a [lit-element](http://lit.dev) wrapper that adds type-safe I/
24
25
  npm i element-vir
25
26
  ```
26
27
 
28
+ Make sure to install this as a normal dependency (not just a dev dependency) because it needs to exist at run time.
29
+
27
30
  # Usage
28
31
 
29
32
  Most usage of this package is done through the [`defineFunctionalElement` function](https://github.com/electrovir/element-vir/blob/main/src/functional-element/define-functional-element.ts#L25-L30). See the [`FunctionalElementInit` type](https://github.com/electrovir/element-vir/blob/main/src/functional-element/functional-element-init.ts#L7-L20) for that function's inputs. These inputs are also described below with examples.
30
33
 
31
- All of [`lit`](https://lit.dev)'s syntax and functionality is also available for use.
34
+ All of [`lit`](https://lit.dev)'s syntax and functionality is also available for use if you wish.
32
35
 
33
36
  ## Simple element definition
34
37
 
35
- Use `defineFunctionalElement` to define your element. This is apparent if you inspect the types but it must be given an object with at least `tagName` and `renderCallback` properties. This is a bare-minimum example custom element:
38
+ Use `defineFunctionalElement` to define your element. Tt must be given an object with at least `tagName` and `renderCallback` properties (the types enforce this). Here is a bare-minimum example custom element:
36
39
 
37
40
  <!-- example-link: src/readme-examples/my-simple.element.ts -->
38
41
 
@@ -47,7 +50,7 @@ export const MySimpleElement = defineFunctionalElement({
47
50
  });
48
51
  ```
49
52
 
50
- Make sure to export your element definition as that will be used to instantiate it in your HTML templates.
53
+ Make sure to export your element definition if you need to use it in other files.
51
54
 
52
55
  ## Using in other elements
53
56
 
@@ -70,17 +73,16 @@ export const MyAppElement = defineFunctionalElement({
70
73
 
71
74
  This requirement ensures that the element is properly imported and registered with the browser. (Compare to pure [lit](http://lit.dev) where you must remember to import each element file as a side effect, or without actually referencing any of its exports in your code.)
72
75
 
73
- If you wish to bypass this interpolation requirement, instead [import `html` directly from `lit`](https://lit.dev/docs/components/overview/).
76
+ If you wish to bypass this interpolation, make sure to [import the `html` tagged template directly from `lit`](https://lit.dev/docs/components/overview/), `import {html} from 'lit';`, instead of version contained in `element-vir`.
74
77
 
75
78
  ## Adding styles
76
79
 
77
- Styles are added through `styles` when defining a functional element (similar to [how they are defined in `lit`](https://lit.dev/docs/components/styles/)):
80
+ Styles are added through the `styles` property when defining a functional element (similar to [how they are defined in `lit`](https://lit.dev/docs/components/styles/)):
78
81
 
79
82
  <!-- example-link: src/readme-examples/my-simple-app-with-styles.element.ts -->
80
83
 
81
84
  ```TypeScript
82
- import {css} from 'lit';
83
- import {defineFunctionalElement, html} from 'element-vir';
85
+ import {css, defineFunctionalElement, html} from 'element-vir';
84
86
 
85
87
  export const MySimpleWithStylesElement = defineFunctionalElement({
86
88
  tagName: 'my-simple-with-styles-element',
@@ -102,9 +104,32 @@ export const MySimpleWithStylesElement = defineFunctionalElement({
102
104
  });
103
105
  ```
104
106
 
107
+ ### Element definition as style selector
108
+
109
+ Functional element definitions can be used in the `css` tagged template just like in the `html` tagged template. This will be replaced by the element's tag name:
110
+
111
+ <!-- example-link: src/readme-examples/my-simple-app-with-styles-and-interpolated-selector.element.ts -->
112
+
113
+ ```TypeScript
114
+ import {css, defineFunctionalElement, html} from 'element-vir';
115
+ import {MySimpleElement} from './my-simple.element';
116
+
117
+ export const MySimpleWithStylesAndInterpolatedSelectorElement = defineFunctionalElement({
118
+ tagName: 'my-simple-with-styles-and-interpolated-selector-element',
119
+ styles: css`
120
+ ${MySimpleElement} {
121
+ background-color: blue;
122
+ }
123
+ `,
124
+ renderCallback: () => html`
125
+ <${MySimpleElement}></${MySimpleElement}>
126
+ `,
127
+ });
128
+ ```
129
+
105
130
  ## Defining and using properties (inputs)
106
131
 
107
- Define properties with `props` when defining a functional element. Each property must be given a default value. If you wish to leave the property's default value as `undefined`, give it a type as well (shown below with `as string | undefined`) so you can assign a defined value of that type to it later.
132
+ Define element properties with `props` when making a functional element. Each property must be given a default value. If you wish to leave the property's default value as `undefined`, give it a type as well (shown below with `as string | undefined`) so you can assign a defined value of that type to it later.
108
133
 
109
134
  To use a custom element's properties, grab `props` from `renderCallback`'s parameters and interpolate it into your HTML template:
110
135
 
@@ -148,38 +173,37 @@ export const MyAppWithPropsElement = defineFunctionalElement({
148
173
  });
149
174
  ```
150
175
 
151
- ## Defining and dispatching custom events (outputs)
176
+ ## Element events (outputs)
152
177
 
153
- Define events with `events` when defining a functional element. Each event must be initialized with `eventInit` and a type parameter. `eventInit` accepts no inputs as it doesn't make sense for events to have a default value.
178
+ Define events with `events` when making a functional element. Each event must be initialized with `defineElementEvent` and a type parameter. `defineElementEvent` accepts no inputs as it doesn't make sense for events to have default values.
154
179
 
155
- To dispatch an event, grab `dispatchEvent` from `renderCallback`'s parameters.
180
+ To dispatch an event, grab `dispatch` from `renderCallback`'s parameters.
156
181
 
157
182
  <!-- example-link: src/readme-examples/my-simple-with-events.element.ts -->
158
183
 
159
184
  ```TypeScript
160
- import {defineFunctionalElement, ElementEvent, eventInit, html} from 'element-vir';
185
+ import {defineElementEvent, defineFunctionalElement, html, listen} from 'element-vir';
161
186
 
162
187
  export const MySimpleWithEventsElement = defineFunctionalElement({
163
188
  tagName: 'my-simple-element-with-events',
164
189
  events: {
165
- logoutClick: eventInit<void>(),
166
- randomNumber: eventInit<number>(),
190
+ logoutClick: defineElementEvent<void>(),
191
+ randomNumber: defineElementEvent<number>(),
167
192
  },
168
- renderCallback: ({props, dispatchEvent, events}) => html`
169
- <!-- normal DOM events must be listened to with the "@" keyword from lit. -->
170
- <button @click=${() => dispatchEvent(new ElementEvent(events.logoutClick, undefined))}>
193
+ renderCallback: ({dispatch, events}) => html`
194
+ <button ${listen('click', () => dispatch(new events.logoutClick(undefined)))}>
171
195
  log out
172
196
  </button>
173
- <button @click=${() => dispatchEvent(new ElementEvent(events.randomNumber, Math.random()))}>
197
+ <button ${listen('click', () => dispatch(new events.randomNumber(Math.random())))}>
174
198
  generate random number
175
199
  </button>
176
200
  `,
177
201
  });
178
202
  ```
179
203
 
180
- ## Listening to custom events (outputs)
204
+ ## Listening to typed events (outputs)
181
205
 
182
- Use the `listen` directive to listen to custom events emitted by your custom functional elements:
206
+ Use the `listen` directive to listen to typed events emitted by your custom functional elements:
183
207
 
184
208
  <!-- example-link: src/readme-examples/my-app-with-events.element.ts -->
185
209
 
@@ -208,15 +232,59 @@ export const MyAppWithEventsElement = defineFunctionalElement({
208
232
  });
209
233
  ```
210
234
 
235
+ `listen` can also be used to listen to native DOM events (like `click`) and the proper event type will be provided for the listener callback.
236
+
237
+ ## Typed events without an element
238
+
239
+ Create a custom event type with `defineTypedEvent`. Make sure to include the type generic (like this: `defineTypedEvent<number>`) and call it twice, the second time with the event type string, (like this: `defineTypedEvent<number>()('my-event-type-name')`) to ensure type safety when using your event. Note that event type names should probably be unique, or they may clash with each other.
240
+
241
+ ### Creating a typed event
242
+
243
+ <!-- example-link: src/readme-examples/custom-event-no-element.ts -->
244
+
245
+ ```TypeScript
246
+ import {defineTypedEvent} from 'element-vir';
247
+
248
+ export const MyCustomEvent = defineTypedEvent<number>()('myCustomEventName');
249
+ ```
250
+
251
+ ### Using a typed event
252
+
253
+ Both dispatching a custom event and listening to a custom event:
254
+
255
+ <!-- example-link: src/readme-examples/custom-event-usage.element.ts -->
256
+
257
+ ```TypeScript
258
+ import {defineFunctionalElement, html, listen} from 'element-vir';
259
+ import {MyCustomEvent} from './custom-event-no-element';
260
+
261
+ export const MyElementWithCustomEvents = defineFunctionalElement({
262
+ tagName: 'my-app-with-custom-events',
263
+ renderCallback: ({genericDispatch}) => html`
264
+ <div
265
+ ${listen(MyCustomEvent, (event) => {
266
+ console.log(`Got a number! ${event.detail}`);
267
+ })}
268
+ >
269
+ <div
270
+ ${listen('click', () => {
271
+ genericDispatch(new MyCustomEvent(Math.random()));
272
+ })}
273
+ ></div>
274
+ </div>
275
+ `,
276
+ });
277
+ ```
278
+
211
279
  ## Directives
212
280
 
213
- Some custom [`lit` directives](https://lit.dev/docs/templates/custom-directives/) are also contained within this package.
281
+ The following custom [`lit` directives](https://lit.dev/docs/templates/custom-directives/) are contained within this package.
214
282
 
215
283
  ### onDomCreated
216
284
 
217
285
  This directive should be used instead of trying to use `querySelector` directly on the custom element.
218
286
 
219
- This triggers only once when the element it's contained within is created in the DOM. If it's containing element changes, the callback will be triggered again.
287
+ This triggers only once when the element it's attached has actually been created in the DOM. If it's attached element changes, the callback will be triggered again.
220
288
 
221
289
  <!-- example-link: src/readme-examples/my-simple-with-on-dom-created.element.ts -->
222
290
 
@@ -240,7 +308,7 @@ export const MySimpleWithOnDomCreatedElement = defineFunctionalElement({
240
308
 
241
309
  ### onResize
242
310
 
243
- This directive fulfills a common use case of triggering callbacks when something resizes. Instead of just tracking the _globally_ resizing window though, this allows you to track resizes of an individual element. The callback here is given a portion of the [`ResizeObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry) (since not all properties are supported well in browsers).
311
+ This directive fulfills a common use case of triggering callbacks when something resizes. Instead of just tracking the _globally_ resizing window though, this allows you to track resizes of an individual element. The callback here is passed an object with a portion of the [`ResizeObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry) properties (since not all properties are supported well in browsers).
244
312
 
245
313
  <!-- example-link: src/readme-examples/my-simple-with-on-resize.element.ts -->
246
314
 
@@ -273,13 +341,12 @@ Listen to a specific event emitted from a custom element. This is explained in t
273
341
 
274
342
  ### assignWithCleanup
275
343
 
276
- This directive is the same as the `assign` directive but it accepts an additional `cleanupCallback` input. Use this directive to assign values which need some kind of cleanup if they're overwritten. For example, a 3D rendering engine which uses the canvas that should free up memory when it's swapped out.
344
+ This directive is the same as the `assign` directive but it accepts an additional `cleanupCallback` input. Use this directive to assign values which need some kind of cleanup when they're overwritten. For example, a 3D rendering engine which uses the canvas that should free up memory when it's swapped out.
277
345
 
278
346
  <!-- example-link: src/readme-examples/my-app-with-cleanup.element.ts -->
279
347
 
280
348
  ```TypeScript
281
- import {assign, defineFunctionalElement, html} from 'element-vir';
282
- import {assignWithCleanup} from '../functional-element/directives/assign-with-clean-up.directive';
349
+ import {assign, assignWithCleanup, defineFunctionalElement, html} from 'element-vir';
283
350
  import {MySimpleWithPropsElement} from './my-simple-with-props.element';
284
351
 
285
352
  export const MyAppWithPropsElement = defineFunctionalElement({
@@ -310,7 +377,13 @@ To require all child elements to be functional elements defined by this package,
310
377
  <!-- example-link: src/readme-examples/require-functional-element.ts -->
311
378
 
312
379
  ```TypeScript
313
- import {requireAllCustomElementsToBeFunctionalElement} from '../require-functional-element';
380
+ import {requireAllCustomElementsToBeFunctionalElement} from 'element-vir';
314
381
 
315
382
  requireAllCustomElementsToBeFunctionalElement();
316
383
  ```
384
+
385
+ # Dev
386
+
387
+ ## markdown out of date
388
+
389
+ If you see this: `Code in Markdown file(s) is out of date. Run without --check to update. code-in-markdown failed.`, run `npm run update-docs` to fix it.
@@ -27,7 +27,6 @@ export function defineFunctionalElement(functionalElementInit) {
27
27
  },
28
28
  _a.tagName = functionalElementInit.tagName,
29
29
  _a.styles = functionalElementInit.styles || css ``,
30
- _a.propNames = Object.keys(functionalElementInit.props || {}),
31
30
  _a.events = eventsMap,
32
31
  _a.renderCallback = functionalElementInit.renderCallback,
33
32
  _a.props = createPropertyDescriptorMap(functionalElementInit.props),
@@ -1,10 +1,15 @@
1
1
  import { PartInfo } from 'lit/directive.js';
2
2
  import { ElementEvent, EventDescriptor } from '../element-events';
3
3
  /**
4
- * The directive generics (in listenDirective) are not strong enough to maintain their values. Thus,
5
- * the directive call is wrapped in this function.
4
+ * Listen to element events.
5
+ *
6
+ * @param eventDescriptor Needs to come either from a functional element (like
7
+ * MyFunctionalElement.events.eventName) or from a custom element event created via the
8
+ * createCustomEvent function.
9
+ * @param listener The callback to fire when an event is caught. Assuming the eventDescriptor input
10
+ * is properly typed, the event given to this callback will also be typed.
6
11
  */
7
- export declare function listen<EventName extends string, DetailType>(eventType: EventDescriptor<EventName, DetailType>, listener: (event: ElementEvent<EventName, DetailType>) => void): import("lit-html/directive").DirectiveResult<{
12
+ export declare function listen<EventName extends string, DetailType>(eventDescriptor: EventDescriptor<EventName, DetailType>, listener: (event: ElementEvent<EventName, DetailType>) => void): import("lit-html/directive").DirectiveResult<{
8
13
  new (partInfo: PartInfo): {
9
14
  readonly element: HTMLElement;
10
15
  lastListenerMetaData: ListenerMetaData<unknown> | undefined;
@@ -2,12 +2,21 @@ import { noChange } from 'lit';
2
2
  import { directive, Directive } from 'lit/directive.js';
3
3
  import { extractElement } from './directive-util';
4
4
  /**
5
- * The directive generics (in listenDirective) are not strong enough to maintain their values. Thus,
6
- * the directive call is wrapped in this function.
5
+ * Listen to element events.
6
+ *
7
+ * @param eventDescriptor Needs to come either from a functional element (like
8
+ * MyFunctionalElement.events.eventName) or from a custom element event created via the
9
+ * createCustomEvent function.
10
+ * @param listener The callback to fire when an event is caught. Assuming the eventDescriptor input
11
+ * is properly typed, the event given to this callback will also be typed.
7
12
  */
8
- export function listen(eventType, listener) {
9
- return listenDirective(eventType, listener);
13
+ export function listen(eventDescriptor, listener) {
14
+ return listenDirective(eventDescriptor, listener);
10
15
  }
16
+ /**
17
+ * The directive generics here are not strong enough to maintain their values. Thus, the directive
18
+ * call is wrapped in the function above.
19
+ */
11
20
  const listenDirective = directive(class extends Directive {
12
21
  constructor(partInfo) {
13
22
  super(partInfo);
@@ -0,0 +1,15 @@
1
+ import { DirectiveResult } from 'lit/directive.js';
2
+ /**
3
+ * Listen to events. First parameter is just a string for an event name/type, vs. the more
4
+ * complicated first parameter type for the "listen" directive. If the given eventName is a native
5
+ * DOM event, the event type given to the callback will be the event type associated with that
6
+ * native event type. Otherwise, specific event types given to the callback are unknown.
7
+ *
8
+ * If you are listening to your custom events, it is better to use the "listen" directive directly
9
+ * so your callbacks are more tightly typed.
10
+ *
11
+ * @param eventName Name of the event to listen to.
12
+ * @param listener The callback to fire when an event is caught.
13
+ */
14
+ export declare function namedListen<EventName extends keyof HTMLElementEventMap>(eventName: EventName, listener: (event: HTMLElementEventMap[EventName]) => void): DirectiveResult;
15
+ export declare function namedListen<EventName extends string>(eventName: EventName, listener: (event: Event) => void): DirectiveResult;
@@ -0,0 +1,42 @@
1
+ import { noChange } from 'lit';
2
+ import { directive, Directive } from 'lit/directive.js';
3
+ import { extractElement } from './directive-util';
4
+ export function namedListen(eventName, listener) {
5
+ return listenDirective(eventName, listener);
6
+ }
7
+ const listenDirective = directive(class extends Directive {
8
+ constructor(partInfo) {
9
+ super(partInfo);
10
+ this.element = extractElement(partInfo, 'listen', HTMLElement);
11
+ }
12
+ resetListener(listenerMetaData) {
13
+ if (this.lastListenerMetaData) {
14
+ this.element.removeEventListener(this.lastListenerMetaData.eventType, this.lastListenerMetaData.listener);
15
+ }
16
+ this.element.addEventListener(listenerMetaData.eventType, listenerMetaData.listener);
17
+ this.lastListenerMetaData = listenerMetaData;
18
+ }
19
+ createListenerMetaData(eventType, callback) {
20
+ return {
21
+ eventType,
22
+ callback,
23
+ listener: (event) => { var _a; return (_a = this.lastListenerMetaData) === null || _a === void 0 ? void 0 : _a.callback(event); },
24
+ };
25
+ }
26
+ render(eventName, callback) {
27
+ if (typeof eventName !== 'string') {
28
+ throw new Error(`Cannot listen to an event with a name that is not a string. Given event name: "${eventName}"`);
29
+ }
30
+ if (this.lastListenerMetaData && this.lastListenerMetaData.eventType === eventName) {
31
+ /**
32
+ * Store the callback here so we don't have to update the attached listener every
33
+ * time the callback is updated.
34
+ */
35
+ this.lastListenerMetaData.callback = callback;
36
+ }
37
+ else {
38
+ this.resetListener(this.createListenerMetaData(eventName, callback));
39
+ }
40
+ return noChange;
41
+ }
42
+ });
@@ -11,8 +11,8 @@ export const onDomCreated = directive(class extends Directive {
11
11
  assertsIsElementPartInfo(partInfo, directiveName);
12
12
  const newElement = partInfo.element;
13
13
  if (newElement !== this.element) {
14
- // use setTimeout here so it can fire property changes outside of a render loop
15
- setTimeout(() => callback(newElement), 0);
14
+ // use requestAnimationFrame here so it can fire property changes outside of a render loop
15
+ requestAnimationFrame(() => callback(newElement));
16
16
  this.element = newElement;
17
17
  }
18
18
  return this.render(callback);
@@ -7,10 +7,11 @@ export declare type EventInitInfo<EventNameGeneric extends string> = {
7
7
  eventName: EventNameGeneric;
8
8
  };
9
9
  export declare class ElementEvent<EventName extends string, EventValue> extends CustomEvent<EventValue> {
10
- protected readonly eventInitInfo: EventInitInfo<EventName>;
10
+ readonly eventInitInfo: EventInitInfo<EventName>;
11
11
  readonly eventName: string;
12
12
  constructor(eventInitInfo: EventInitInfo<EventName>, initDetail: EventValue);
13
13
  }
14
+ export declare function defineCustomEvent<EventName extends string, EventValue>(eventName: EventName): (new (eventValue: EventValue) => ElementEvent<EventName, EventValue>) & EventDescriptor<EventName, EventValue>;
14
15
  export declare type EventExtraProperties<DetailType> = {
15
16
  /**
16
17
  * The event constructor property is needed in order to store sufficient type data for event
@@ -10,6 +10,19 @@ export class ElementEvent extends CustomEvent {
10
10
  this.eventName = String(this.eventInitInfo.eventName);
11
11
  }
12
12
  }
13
+ export function defineCustomEvent(eventName) {
14
+ var _a;
15
+ return _a = class extends ElementEvent {
16
+ constructor(eventValue) {
17
+ super({ eventName: eventName }, eventValue);
18
+ window.ElementEvent = ElementEvent;
19
+ }
20
+ },
21
+ _a.eventName = eventName,
22
+ // this allows sub classes of ElementEvent to be directly listened to
23
+ _a.eventConstructor = ElementEvent.constructor,
24
+ _a;
25
+ }
13
26
  export function createEventDescriptorMap(eventsInit) {
14
27
  if (!eventsInit) {
15
28
  return {};
@@ -20,7 +20,6 @@ export declare type FunctionalElementInit<PropertyInitGeneric extends PropertyIn
20
20
  export declare abstract class FunctionalElementBaseClass<PropertyInitGeneric extends PropertyInitMapBase> extends LitElement {
21
21
  static readonly tagName: string;
22
22
  static readonly styles: CSSResult;
23
- static readonly propNames: string[];
24
23
  abstract render(): TemplateResult | Promise<TemplateResult>;
25
24
  abstract readonly instanceProps: PropertyInitGeneric;
26
25
  }
@@ -37,5 +36,4 @@ export declare type ExtraStaticFunctionalElementProperties<PropertyInitGeneric e
37
36
  */
38
37
  tagName: string;
39
38
  styles: CSSResult;
40
- propNames: string[];
41
39
  }>;
@@ -6,11 +6,12 @@ export declare type RenderCallback<PropertyInitGeneric extends PropertyInitMapBa
6
6
  export declare type RenderParams<PropertyInitGeneric extends PropertyInitMapBase, EventsInitGeneric extends EventsInitMap> = {
7
7
  props: PropertyInitGeneric;
8
8
  events: EventDescriptorMap<EventsInitGeneric>;
9
- dispatchEvent: <EventName extends keyof EventsInitGeneric>(event: ElementEvent<EventName extends string ? EventName : never, EventInitMapEventDetailExtractor<EventName, EventsInitGeneric>>) => boolean;
9
+ host: FunctionalElementInstance<PropertyInitGeneric>;
10
+ dispatchElementEvent: <EventName extends keyof EventsInitGeneric>(event: ElementEvent<EventName extends string ? EventName : never, EventInitMapEventDetailExtractor<EventName, EventsInitGeneric>>) => boolean;
10
11
  /**
11
- * Same as dispatchEvent but without the extra types. This allows you to emit any events, even
12
- * events from other custom elements.
12
+ * Same as dispatchElementEvent but without the extra types. This allows you to emit any events,
13
+ * even events from other custom elements.
13
14
  */
14
- defaultDispatchEvent: (event: Event) => boolean;
15
+ dispatchEvent: (event: Event) => boolean;
15
16
  };
16
17
  export declare function createRenderParams<PropertyInitGeneric extends PropertyInitMapBase, EventsInitGeneric extends EventsInitMap>(element: FunctionalElementInstance<PropertyInitGeneric>, eventsMap: EventDescriptorMap<EventsInitGeneric>): RenderParams<PropertyInitGeneric, EventsInitGeneric>;
@@ -4,8 +4,9 @@ export function createRenderParams(element, eventsMap) {
4
4
  * These two dispatch properties do the same thing but their interfaces are different.
5
5
  * DispatchEvent's type interface is much stricter.
6
6
  */
7
+ dispatchElementEvent: (event) => element.dispatchEvent(event),
7
8
  dispatchEvent: (event) => element.dispatchEvent(event),
8
- defaultDispatchEvent: (event) => element.dispatchEvent(event),
9
+ host: element,
9
10
  props: element.instanceProps,
10
11
  events: eventsMap,
11
12
  };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export * from './functional-element/define-functional-element';
2
2
  export * from './functional-element/directives/assign-with-clean-up.directive';
3
3
  export * from './functional-element/directives/assign.directive';
4
4
  export * from './functional-element/directives/listen.directive';
5
+ export * from './functional-element/directives/named-listen.directive';
5
6
  export * from './functional-element/directives/on-dom-created.directive';
6
7
  export * from './functional-element/directives/on-resize.directive';
7
8
  export * from './functional-element/element-events';
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ export * from './functional-element/define-functional-element';
2
2
  export * from './functional-element/directives/assign-with-clean-up.directive';
3
3
  export * from './functional-element/directives/assign.directive';
4
4
  export * from './functional-element/directives/listen.directive';
5
+ export * from './functional-element/directives/named-listen.directive';
5
6
  export * from './functional-element/directives/on-dom-created.directive';
6
7
  export * from './functional-element/directives/on-resize.directive';
7
8
  export * from './functional-element/element-events';
package/index.html ADDED
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Element Vir Test</title>
5
+ <script type="module" src="/src/test/elements/app.element.ts"></script>
6
+ <link rel="stylesheet" type="text/css" href="/index.css" />
7
+ <script>
8
+ console.log('yo');
9
+ </script>
10
+ </head>
11
+ <body>
12
+ <element-vir-test-app></element-vir-test-app>
13
+ </body>
14
+ </html>
@@ -0,0 +1,32 @@
1
+ import {dirname, join} from 'path';
2
+ import {pathsToModuleNameMapper} from 'ts-jest';
3
+ import {InitialOptionsTsJest} from 'ts-jest/dist/types';
4
+ import {findTsConfigFile, getTsconfigPathAliases} from './read-tsconfig';
5
+
6
+ const repoRootDir = dirname(__dirname);
7
+
8
+ const config: InitialOptionsTsJest = {
9
+ preset: 'ts-jest',
10
+ testEnvironment: 'jsdom',
11
+ verbose: false,
12
+ // type tests are caught by the typescript compiler
13
+ testPathIgnorePatterns: ['.type.test.ts'],
14
+ rootDir: repoRootDir,
15
+ silent: false,
16
+ moduleNameMapper: pathsToModuleNameMapper(getTsconfigPathAliases(), {
17
+ prefix: '<rootDir>/',
18
+ }) as Record<string, string | string[]>,
19
+ roots: [join(repoRootDir, 'src')],
20
+ setupFilesAfterEnv: [join(__dirname, 'jest.setup.ts')],
21
+ globals: {
22
+ 'ts-jest': {
23
+ tsconfig: findTsConfigFile(),
24
+ diagnostics: {
25
+ warnOnly: true,
26
+ ignoreCodes: ['TS151001'],
27
+ },
28
+ },
29
+ },
30
+ };
31
+
32
+ export default config;
@@ -0,0 +1,12 @@
1
+ import {CustomConsole, LogMessage, LogType} from '@jest/console';
2
+
3
+ function simpleFormatter(type: LogType, message: LogMessage): string {
4
+ const consoleIndent = ' ';
5
+
6
+ return message
7
+ .split(/\n/)
8
+ .map((line) => consoleIndent + line)
9
+ .join('\n');
10
+ }
11
+
12
+ global.console = new CustomConsole(process.stdout, process.stderr, simpleFormatter);
@@ -0,0 +1,27 @@
1
+ import {dirname} from 'path';
2
+ import {findConfigFile, parseJsonConfigFileContent, readConfigFile, sys as tsSys} from 'typescript';
3
+
4
+ export function findTsConfigFile(): string {
5
+ const dirToCheck = __dirname;
6
+ const configFileName = findConfigFile(dirToCheck, tsSys.fileExists, 'tsconfig.json');
7
+ if (!configFileName) {
8
+ throw new Error(
9
+ `Could not find tsconfig.json file from starting search at "${dirToCheck}"`,
10
+ );
11
+ }
12
+ return configFileName;
13
+ }
14
+
15
+ export function getTsconfigPathAliases() {
16
+ const configFileName = findTsConfigFile();
17
+
18
+ if (!configFileName) {
19
+ throw new Error(`Failed to find tsconfig.`);
20
+ }
21
+
22
+ const configFile = readConfigFile(configFileName, tsSys.readFile);
23
+ const tsConfig = parseJsonConfigFileContent(configFile.config, tsSys, dirname(configFileName));
24
+ const tsConfigPaths = tsConfig.options.paths || {};
25
+
26
+ return tsConfigPaths;
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "element-vir",
3
- "version": "2.0.4",
3
+ "version": "4.0.0",
4
4
  "keywords": [
5
5
  "custom",
6
6
  "web",
@@ -25,23 +25,28 @@
25
25
  "main": "dist/index.js",
26
26
  "types": "dist/index.d.ts",
27
27
  "scripts": {
28
- "compile": "virmator compile",
28
+ "compile": "tsc",
29
29
  "format": "virmator format write",
30
+ "jest": "jest --config ./jest/jest.config.ts",
30
31
  "prepublishOnly": "npm run test:full",
31
32
  "spellcheck": "virmator spellcheck",
32
- "start": "npm install && snowpack dev",
33
- "test": "npm run compile",
33
+ "start": "npm install && vite --force --config ./vite/vite.config.ts",
34
+ "test": "npm run type-check && npm run jest",
34
35
  "test:full": "npm test && npm run spellcheck && virmator format check && npm run update-docs -- --check",
35
- "update-docs": "virmator code-in-markdown README.md"
36
+ "type-check": "tsc --noEmit",
37
+ "update-docs": "virmator code-in-markdown README.md --index src/index.ts"
36
38
  },
37
39
  "dependencies": {
38
- "augment-vir": "^1.4.1",
39
- "lit": "^2.0.2"
40
+ "augment-vir": "^1.4.3",
41
+ "lit": "^2.1.1"
40
42
  },
41
43
  "devDependencies": {
42
- "@snowpack/plugin-typescript": "^1.2.1",
43
- "snowpack": "^3.8.8",
44
- "typescript": "^4.4.4",
45
- "virmator": "^1.3.7"
44
+ "@types/jest": "^27.4.0",
45
+ "jest": "^27.4.7",
46
+ "ts-jest": "^27.1.3",
47
+ "ts-node": "^10.4.0",
48
+ "typescript": "^4.5.4",
49
+ "virmator": "^1.3.7",
50
+ "vite": "^2.7.12"
46
51
  }
47
52
  }
@@ -0,0 +1,7 @@
1
+ html,
2
+ body {
3
+ margin: 0;
4
+ padding: 0;
5
+ height: 100%;
6
+ width: 100%;
7
+ }
@@ -0,0 +1,90 @@
1
+ import chalk from 'chalk';
2
+ import {existsSync, lstatSync, readlinkSync} from 'fs';
3
+ import {relative} from 'path';
4
+ import {PluginOption} from 'vite';
5
+
6
+ /**
7
+ * Include actual paths and symlinked target paths if they exist.
8
+ *
9
+ * This is needed because when removing files from the watcher, sym links have be removed with the
10
+ * path to the symlink itself AND the path to the symlink target or the path will still be watched.
11
+ */
12
+ function mapToActualPaths(paths: Readonly<string[]>): Readonly<string[]> {
13
+ return paths.reduce((accum, path) => {
14
+ if (existsSync(path)) {
15
+ if (lstatSync(path).isSymbolicLink()) {
16
+ console.info('reading symlink from', path);
17
+ // sym links AND the original path both need to be included
18
+ return accum.concat(readlinkSync(path), path);
19
+ } else {
20
+ return accum.concat(path);
21
+ }
22
+ } else {
23
+ return accum;
24
+ }
25
+ }, [] as string[]);
26
+ }
27
+
28
+ /**
29
+ * There are similar plugins out there that try to do this but they aren't aggressive enough. This
30
+ * plugin literally always reloads on save, no questions asked.
31
+ */
32
+ export function alwaysReloadPlugin(
33
+ config: Partial<{
34
+ exclusions: string[];
35
+ /** Inclusions apply after exclusions so they will override exclusions. */
36
+ inclusions: string[];
37
+ root: string;
38
+ }> = {},
39
+ ): PluginOption {
40
+ return {
41
+ name: 'alwaysReloadPlugin',
42
+ apply: 'serve',
43
+ config: () => ({server: {watch: {disableGlobbing: false}}}),
44
+ configureServer({watcher, ws, config: {logger}}) {
45
+ const {root = process.cwd(), inclusions = [], exclusions = []} = config;
46
+ let callingAlready = false;
47
+ let loggedAlready = false;
48
+
49
+ const reloadCallback = (path: string) => {
50
+ if (!loggedAlready) {
51
+ loggedAlready = true;
52
+ // log watched stuff so that we can make sure it's not watching too much
53
+ // console.info({watched: watcher.getWatched()});
54
+ }
55
+ // prevent duplicate calls cause the watcher is very eager to call callbacks multiple times in a row
56
+ if (!callingAlready) {
57
+ callingAlready = true;
58
+ ws.send({
59
+ type: 'full-reload',
60
+ path: '*',
61
+ });
62
+ logger.info(
63
+ `${chalk.green('page reload')} ${chalk.dim(relative(root, path))}`,
64
+ {clear: true, timestamp: true},
65
+ );
66
+ /**
67
+ * Debounce reloads calls so that they don't get spammed. If you're saving
68
+ * faster than this, then what the heck are you doing anyway?
69
+ */
70
+ setTimeout(() => {
71
+ callingAlready = false;
72
+ }, 100);
73
+ }
74
+ };
75
+
76
+ if (exclusions.length) {
77
+ watcher.unwatch(mapToActualPaths(exclusions));
78
+ // ignore macOS file system metadata stuff
79
+ watcher.unwatch('./**/.DS_Store');
80
+ }
81
+ if (inclusions.length) {
82
+ watcher.add(inclusions);
83
+ }
84
+ if (!watcher.listeners('change').includes(reloadCallback)) {
85
+ watcher.on('change', reloadCallback);
86
+ watcher.on('add', reloadCallback);
87
+ }
88
+ },
89
+ };
90
+ }
@@ -0,0 +1,10 @@
1
+ import {dirname} from 'path';
2
+ import {alwaysReloadPlugin} from './always-reload-plugin';
3
+
4
+ const viteConfig = {
5
+ clearScreen: false,
6
+ plugins: [alwaysReloadPlugin()],
7
+ rootDir: dirname(__dirname),
8
+ };
9
+
10
+ export default viteConfig;