@prismicio/simulator 0.1.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.
Files changed (127) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +98 -0
  3. package/dist/SimulatorAPI.cjs +50 -0
  4. package/dist/SimulatorAPI.cjs.map +1 -0
  5. package/dist/SimulatorAPI.d.ts +12 -0
  6. package/dist/SimulatorAPI.js +50 -0
  7. package/dist/SimulatorAPI.js.map +1 -0
  8. package/dist/SimulatorClient.cjs +54 -0
  9. package/dist/SimulatorClient.cjs.map +1 -0
  10. package/dist/SimulatorClient.d.ts +10 -0
  11. package/dist/SimulatorClient.js +54 -0
  12. package/dist/SimulatorClient.js.map +1 -0
  13. package/dist/channel/ChannelEmitter.cjs +146 -0
  14. package/dist/channel/ChannelEmitter.cjs.map +1 -0
  15. package/dist/channel/ChannelEmitter.d.ts +38 -0
  16. package/dist/channel/ChannelEmitter.js +146 -0
  17. package/dist/channel/ChannelEmitter.js.map +1 -0
  18. package/dist/channel/ChannelNetwork.cjs +112 -0
  19. package/dist/channel/ChannelNetwork.cjs.map +1 -0
  20. package/dist/channel/ChannelNetwork.d.ts +24 -0
  21. package/dist/channel/ChannelNetwork.js +112 -0
  22. package/dist/channel/ChannelNetwork.js.map +1 -0
  23. package/dist/channel/ChannelReceiver.cjs +95 -0
  24. package/dist/channel/ChannelReceiver.cjs.map +1 -0
  25. package/dist/channel/ChannelReceiver.d.ts +20 -0
  26. package/dist/channel/ChannelReceiver.js +95 -0
  27. package/dist/channel/ChannelReceiver.js.map +1 -0
  28. package/dist/channel/errors.cjs +54 -0
  29. package/dist/channel/errors.cjs.map +1 -0
  30. package/dist/channel/errors.d.ts +25 -0
  31. package/dist/channel/errors.js +54 -0
  32. package/dist/channel/errors.js.map +1 -0
  33. package/dist/channel/index.d.ts +10 -0
  34. package/dist/channel/messages.cjs +66 -0
  35. package/dist/channel/messages.cjs.map +1 -0
  36. package/dist/channel/messages.d.ts +10 -0
  37. package/dist/channel/messages.js +66 -0
  38. package/dist/channel/messages.js.map +1 -0
  39. package/dist/channel/types.cjs +11 -0
  40. package/dist/channel/types.cjs.map +1 -0
  41. package/dist/channel/types.d.ts +61 -0
  42. package/dist/channel/types.js +13 -0
  43. package/dist/channel/types.js.map +1 -0
  44. package/dist/index.cjs +33 -0
  45. package/dist/index.cjs.map +1 -0
  46. package/dist/index.d.ts +7 -0
  47. package/dist/index.js +27 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/kit/SimulatorManager.cjs +140 -0
  50. package/dist/kit/SimulatorManager.cjs.map +1 -0
  51. package/dist/kit/SimulatorManager.d.ts +15 -0
  52. package/dist/kit/SimulatorManager.js +140 -0
  53. package/dist/kit/SimulatorManager.js.map +1 -0
  54. package/dist/kit/State.cjs +82 -0
  55. package/dist/kit/State.cjs.map +1 -0
  56. package/dist/kit/State.d.ts +25 -0
  57. package/dist/kit/State.js +82 -0
  58. package/dist/kit/State.js.map +1 -0
  59. package/dist/kit/domHelpers.cjs +53 -0
  60. package/dist/kit/domHelpers.cjs.map +1 -0
  61. package/dist/kit/domHelpers.d.ts +9 -0
  62. package/dist/kit/domHelpers.js +53 -0
  63. package/dist/kit/domHelpers.js.map +1 -0
  64. package/dist/kit/eventHandlers.cjs +15 -0
  65. package/dist/kit/eventHandlers.cjs.map +1 -0
  66. package/dist/kit/eventHandlers.d.ts +4 -0
  67. package/dist/kit/eventHandlers.js +15 -0
  68. package/dist/kit/eventHandlers.js.map +1 -0
  69. package/dist/kit/getDefault.cjs +16 -0
  70. package/dist/kit/getDefault.cjs.map +1 -0
  71. package/dist/kit/getDefault.d.ts +8 -0
  72. package/dist/kit/getDefault.js +16 -0
  73. package/dist/kit/getDefault.js.map +1 -0
  74. package/dist/kit/index.cjs +26 -0
  75. package/dist/kit/index.cjs.map +1 -0
  76. package/dist/kit/index.d.ts +8 -0
  77. package/dist/kit/index.js +23 -0
  78. package/dist/kit/index.js.map +1 -0
  79. package/dist/kit/messages.cjs +29 -0
  80. package/dist/kit/messages.cjs.map +1 -0
  81. package/dist/kit/messages.d.ts +1 -0
  82. package/dist/kit/messages.js +29 -0
  83. package/dist/kit/messages.js.map +1 -0
  84. package/dist/kit/types.cjs +9 -0
  85. package/dist/kit/types.cjs.map +1 -0
  86. package/dist/kit/types.d.ts +23 -0
  87. package/dist/kit/types.js +10 -0
  88. package/dist/kit/types.js.map +1 -0
  89. package/dist/lib/EventEmitter.cjs +27 -0
  90. package/dist/lib/EventEmitter.cjs.map +1 -0
  91. package/dist/lib/EventEmitter.d.ts +10 -0
  92. package/dist/lib/EventEmitter.js +27 -0
  93. package/dist/lib/EventEmitter.js.map +1 -0
  94. package/dist/lib/__PRODUCTION__.d.ts +7 -0
  95. package/dist/lib/throttle.cjs +24 -0
  96. package/dist/lib/throttle.cjs.map +1 -0
  97. package/dist/lib/throttle.d.ts +1 -0
  98. package/dist/lib/throttle.js +24 -0
  99. package/dist/lib/throttle.js.map +1 -0
  100. package/dist/types.cjs +14 -0
  101. package/dist/types.cjs.map +1 -0
  102. package/dist/types.d.ts +34 -0
  103. package/dist/types.js +16 -0
  104. package/dist/types.js.map +1 -0
  105. package/package.json +99 -0
  106. package/src/SimulatorAPI.ts +90 -0
  107. package/src/SimulatorClient.ts +96 -0
  108. package/src/channel/ChannelEmitter.ts +278 -0
  109. package/src/channel/ChannelNetwork.ts +216 -0
  110. package/src/channel/ChannelReceiver.ts +184 -0
  111. package/src/channel/errors.ts +53 -0
  112. package/src/channel/index.ts +75 -0
  113. package/src/channel/messages.ts +115 -0
  114. package/src/channel/types.ts +138 -0
  115. package/src/index.ts +42 -0
  116. package/src/kit/SimulatorManager.ts +199 -0
  117. package/src/kit/State.ts +113 -0
  118. package/src/kit/domHelpers.ts +75 -0
  119. package/src/kit/eventHandlers.ts +16 -0
  120. package/src/kit/getDefault.ts +18 -0
  121. package/src/kit/index.ts +33 -0
  122. package/src/kit/messages.ts +25 -0
  123. package/src/kit/types.ts +30 -0
  124. package/src/lib/EventEmitter.ts +41 -0
  125. package/src/lib/__PRODUCTION__.ts +12 -0
  126. package/src/lib/throttle.ts +26 -0
  127. package/src/types.ts +57 -0
@@ -0,0 +1,113 @@
1
+ import type { SharedSlice, SliceZone } from "@prismicio/client";
2
+
3
+ import { EventEmitter } from "../lib/EventEmitter";
4
+ import { __PRODUCTION__ } from "../lib/__PRODUCTION__";
5
+ import { throttle } from "../lib/throttle";
6
+
7
+ import type { ActiveSlice } from "../types";
8
+ import { StateEventType, StateEvents } from "./types";
9
+
10
+ import { getActiveSliceDOM, getSliceZoneDOM } from "./domHelpers";
11
+ import { getDefaultSlices } from "./getDefault";
12
+
13
+ type StateConstructorArgs = {
14
+ slices?: SliceZone;
15
+ };
16
+
17
+ export class State extends EventEmitter<StateEvents> {
18
+ private _slices: SliceZone;
19
+ public set slices(slices: SliceZone) {
20
+ this._slices = slices;
21
+ // Clean up message
22
+ this.message = "";
23
+ // Dispatch event
24
+ this.emit(StateEventType.Slices, slices);
25
+ }
26
+ public get slices(): SliceZone {
27
+ return this._slices;
28
+ }
29
+
30
+ private _activeSlice: ActiveSlice | null;
31
+ public set activeSlice(activeSlice: ActiveSlice | null) {
32
+ this._activeSlice = activeSlice;
33
+ this.emit(StateEventType.ActiveSlice, activeSlice);
34
+ }
35
+ public get activeSlice(): ActiveSlice | null {
36
+ return this._activeSlice;
37
+ }
38
+
39
+ private _message: string;
40
+ public set message(message: string) {
41
+ this._message = message;
42
+ this.emit(StateEventType.Message, message);
43
+ }
44
+ public get message(): string {
45
+ return this._message;
46
+ }
47
+
48
+ private _mouse: { x: number; y: number };
49
+
50
+ constructor(args?: StateConstructorArgs) {
51
+ super();
52
+
53
+ this._slices = args?.slices || getDefaultSlices();
54
+ this._activeSlice = null;
55
+ this._message = "";
56
+
57
+ // StateManager needs to keep track of mouse position for the active slice
58
+ this._mouse = { x: 0, y: 0 };
59
+ }
60
+
61
+ async init(): Promise<void> {
62
+ // Init listener
63
+ window.addEventListener("mousemove", (event) => {
64
+ this._mouse = { x: event.clientX, y: event.clientY };
65
+ });
66
+ }
67
+
68
+ private _setActiveSlice = (): void => {
69
+ // There can't be an active slice if there's no slices displayed
70
+ if (this.slices.length === 0) {
71
+ this.activeSlice = null;
72
+
73
+ return;
74
+ }
75
+
76
+ const $sliceZone = getSliceZoneDOM(this.slices.length);
77
+
78
+ // SliceZone has not been found, therefore active slice cannot be found
79
+ if (!$sliceZone) {
80
+ this.activeSlice = null;
81
+
82
+ return;
83
+ }
84
+
85
+ const $activeSlice = getActiveSliceDOM($sliceZone, this._mouse);
86
+
87
+ // Active slice has not been found
88
+ if (!$activeSlice) {
89
+ this.activeSlice = null;
90
+
91
+ return;
92
+ }
93
+
94
+ const activeSliceIndex = Array.prototype.indexOf.call(
95
+ $sliceZone.children,
96
+ $activeSlice,
97
+ );
98
+
99
+ // Active slice has been found
100
+ this.activeSlice = {
101
+ rect: $activeSlice.getBoundingClientRect(),
102
+ sliceID: this.slices[activeSliceIndex].slice_type,
103
+ variationID: (this.slices[activeSliceIndex] as SharedSlice).variation,
104
+ index: activeSliceIndex,
105
+ };
106
+ };
107
+ setActiveSlice = throttle(this._setActiveSlice, 16);
108
+
109
+ setSliceZone(slices: SliceZone): void {
110
+ // Set slices
111
+ this.slices = slices;
112
+ }
113
+ }
@@ -0,0 +1,75 @@
1
+ export const simulatorClass = "slice-simulator";
2
+ export const simulatorRootClass = "slice-simulator--root";
3
+
4
+ export const getSimulatorDOM = (): Element | null => {
5
+ return document.querySelector(`.${simulatorClass}`);
6
+ };
7
+
8
+ export const getSimulatorRootDOM = (): Element | null => {
9
+ return document.querySelector(`.${simulatorRootClass}`);
10
+ };
11
+
12
+ export const getSliceZoneDOM = (
13
+ expectedNumberOfChildren: number,
14
+ ): Element | null => {
15
+ // If SliceZone has been tagged (#5)
16
+ let node = document.querySelector(`.${simulatorClass} [data-slice-zone]`);
17
+ if (node) {
18
+ if (node.children.length !== expectedNumberOfChildren) {
19
+ console.warn(
20
+ `Flagged SliceZone has an unexpected number of children, found ${node.children.length} but expected ${expectedNumberOfChildren}. This might lead to unexpected behaviors. Are you sure you tagged the right element?`,
21
+ );
22
+ }
23
+
24
+ return node;
25
+ }
26
+
27
+ // Start searching
28
+ node = document.querySelector(`.${simulatorClass} .${simulatorRootClass}`);
29
+
30
+ if (!node) {
31
+ return null;
32
+ }
33
+
34
+ const searchDepth = 5;
35
+ for (let i = 0; i < searchDepth; i++) {
36
+ if (node.children.length === expectedNumberOfChildren) {
37
+ // Return the first node that matches the expected number of children
38
+ return node;
39
+ } else if (node.children.length) {
40
+ // Continue searching if current node has children
41
+ node = node.children[0];
42
+ } else {
43
+ // Stop searching if current node has no children
44
+ break;
45
+ }
46
+ }
47
+
48
+ // Failed to find SliceZone
49
+ return null;
50
+ };
51
+
52
+ export const getActiveSliceDOM = (
53
+ $sliceZone: Element,
54
+ mouse: { x: number; y: number },
55
+ ): Element | null => {
56
+ // Raycast cursor position on DOM
57
+ const raycast = document.elementsFromPoint(mouse.x, mouse.y).reverse();
58
+
59
+ // Find SliceZone in raycast
60
+ const sliceZoneIndex = raycast.indexOf($sliceZone);
61
+
62
+ // SliceZone is not part of raycast
63
+ if (sliceZoneIndex === -1) {
64
+ return null;
65
+ }
66
+
67
+ const $slice = raycast[sliceZoneIndex + 1];
68
+
69
+ // Slice is not part of raycast
70
+ if (!$slice) {
71
+ return null;
72
+ }
73
+
74
+ return $slice;
75
+ };
@@ -0,0 +1,16 @@
1
+ export const disableEventHandler = (event: Event): void => {
2
+ event.preventDefault();
3
+ event.stopPropagation();
4
+ };
5
+
6
+ export const onClickHandler = (
7
+ event: MouseEvent & { path?: HTMLElement[] },
8
+ ): void => {
9
+ if (
10
+ event.path &&
11
+ event.path.slice(0, 5).some((el) => el instanceof HTMLAnchorElement)
12
+ ) {
13
+ event.preventDefault();
14
+ event.stopPropagation();
15
+ }
16
+ };
@@ -0,0 +1,18 @@
1
+ import type { SliceZone } from "@prismicio/client";
2
+
3
+ import { SliceSimulatorProps } from "./types";
4
+
5
+ export const getDefaultProps = (): Required<{
6
+ [K in keyof SliceSimulatorProps]: NonNullable<SliceSimulatorProps[K]>;
7
+ }> => ({
8
+ zIndex: 100,
9
+ background: "#ffffff",
10
+ });
11
+
12
+ export const getDefaultSlices = (): SliceZone => {
13
+ return [];
14
+ };
15
+
16
+ export const getDefaultMessage = (): string => {
17
+ return "";
18
+ };
@@ -0,0 +1,33 @@
1
+ // Main
2
+ export { SimulatorManager } from "./SimulatorManager";
3
+ export { State } from "./State";
4
+
5
+ // Defaults
6
+ export {
7
+ getDefaultProps,
8
+ getDefaultSlices,
9
+ getDefaultMessage,
10
+ } from "./getDefault";
11
+
12
+ // DOM helpers
13
+ export {
14
+ simulatorClass,
15
+ simulatorRootClass,
16
+ getSliceZoneDOM,
17
+ getActiveSliceDOM,
18
+ } from "./domHelpers";
19
+
20
+ // Events
21
+ export { disableEventHandler, onClickHandler } from "./eventHandlers";
22
+
23
+ // Messages
24
+ export { sliceSimulatorAccessedDirectly } from "./messages";
25
+
26
+ // Types
27
+ export { StateEventType } from "./types";
28
+ export type {
29
+ StateEvents,
30
+ SliceSimulatorProps,
31
+ SliceSimulatorState,
32
+ SliceSimulatorOptions,
33
+ } from "./types";
@@ -0,0 +1,25 @@
1
+ const div = (content: string) =>
2
+ `<div style="word-spacing: initial; white-space: pre; line-height: initial; font-family: monospace; color: #ffffff; mix-blend-mode: difference; padding: 1rem; font-size: 1rem;">${content}</div>`;
3
+
4
+ const a = (href: string, label?: string) =>
5
+ `<a href="${href}" style="text-decoration: underline;">${label || href}<a>`;
6
+
7
+ const header =
8
+ " _____ ___ _____ _ __ __ \n / ___// (_)_______ / ___/(_)___ ___ __ __/ /___ _/ /_____ _____\n \\__ \\/ / / ___/ _ \\\\__ \\/ / __ `__ \\/ / / / / __ `/ __/ __ \\/ ___/\n ___/ / / / /__/ __/__/ / / / / / / / /_/ / / /_/ / /_/ /_/ / / \n/____/_/_/\\___/\\___/____/_/_/ /_/ /_/\\__,_/_/\\__,_/\\__/\\____/_/ \n / /_ __ __ / __ \\_____(_)________ ___ (_)____\n / __ \\/ / / / / /_/ / ___/ / ___/ __ `__ \\/ / ___/\n / /_/ / /_/ / / ____/ / / (__ ) / / / / / / /__ \n /_.___/\\__, / /_/ /_/ /_/____/_/ /_/ /_/_/\\___/ \n /____/\n\n";
9
+
10
+ const footer =
11
+ "\n\n\n\n\n\n - The Prismic team";
12
+
13
+ export const sliceSimulatorAccessedDirectly = div(
14
+ [
15
+ header,
16
+ `You're seeing this page because you're accessing Slice Simulator\ndirectly, e.g:\n\n - ${a(
17
+ "http://localhost:3000/slice-simulator",
18
+ )}\n\n\n\nSlice Simulator is not meant to be accessed this way, to preview your\nslices, head over to Slice Machine UI:\n\n - ${a(
19
+ "http://localhost:9999",
20
+ )}\n\n\n\nIf you believe this is an error, please reach out to us:\n\n - ${a(
21
+ "https://github.com/prismicio/slice-machine/issues/new/choose",
22
+ )}`,
23
+ footer,
24
+ ].join(""),
25
+ );
@@ -0,0 +1,30 @@
1
+ import type { SliceZone } from "@prismicio/client";
2
+
3
+ import type { ActiveSlice } from "../types";
4
+
5
+ import type { SimulatorManager } from "./SimulatorManager";
6
+
7
+ export enum StateEventType {
8
+ Slices = "slices",
9
+ ActiveSlice = "activeSlice",
10
+ Message = "message",
11
+ }
12
+
13
+ export type StateEvents = {
14
+ [StateEventType.Slices]: SliceZone;
15
+ [StateEventType.ActiveSlice]: ActiveSlice | null;
16
+ [StateEventType.Message]: string;
17
+ };
18
+
19
+ export interface SliceSimulatorProps {
20
+ zIndex?: number;
21
+ background?: string;
22
+ }
23
+
24
+ export interface SliceSimulatorState {
25
+ manager: SimulatorManager;
26
+ slices: SliceZone;
27
+ message: string;
28
+ }
29
+
30
+ export type SliceSimulatorOptions = SliceSimulatorProps & SliceSimulatorState;
@@ -0,0 +1,41 @@
1
+ export type Listener<T> = (event: T) => void;
2
+
3
+ export type EventMap = {
4
+ [K in string]: unknown;
5
+ };
6
+
7
+ export abstract class EventEmitter<TEventMap = EventMap> {
8
+ private _listeners: {
9
+ [K in keyof TEventMap]?: [Listener<TEventMap[K]>, string | null][];
10
+ } = {};
11
+
12
+ on<TEventType extends keyof TEventMap>(
13
+ event: TEventType,
14
+ listener: Listener<TEventMap[TEventType]>,
15
+ key: string | null = null,
16
+ ): void {
17
+ this._listeners[event] = [
18
+ ...(this._listeners[event] ?? []),
19
+ [listener, key],
20
+ ];
21
+ }
22
+
23
+ off<TEventType extends keyof TEventMap>(
24
+ event: TEventType,
25
+ listenerOrKey: Listener<TEventMap[TEventType]> | string,
26
+ ): void {
27
+ this._listeners[event] = (this._listeners[event] ?? []).filter(
28
+ ([listener, key]) =>
29
+ typeof listenerOrKey === "function"
30
+ ? listener !== listenerOrKey
31
+ : key !== listenerOrKey,
32
+ );
33
+ }
34
+
35
+ emit<TEventType extends keyof TEventMap>(
36
+ event: TEventType,
37
+ payload: TEventMap[TEventType],
38
+ ): void {
39
+ (this._listeners[event] ?? []).forEach((listener) => listener[0](payload));
40
+ }
41
+ }
@@ -0,0 +1,12 @@
1
+ // We need to polyfill process if it doesn't exist, such as in the browser.
2
+ if (typeof process === "undefined") {
3
+ globalThis.process = { env: {} } as typeof process;
4
+ }
5
+
6
+ /**
7
+ * `true` if in the production environment, `false` otherwise.
8
+ *
9
+ * This boolean can be used to perform actions only in development environments,
10
+ * such as logging.
11
+ */
12
+ export const __PRODUCTION__ = process.env.NODE_ENV === "production";
@@ -0,0 +1,26 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
+ export const throttle = <TFn extends (...args: any[]) => any>(
3
+ fn: TFn,
4
+ delay = 16,
5
+ ): ((...args: Parameters<TFn>) => void) => {
6
+ let lastExec = 0;
7
+ let timer: NodeJS.Timeout | null = null;
8
+
9
+ return function (this: unknown, ...args: Parameters<TFn>) {
10
+ const now = Date.now();
11
+ const delta = now - lastExec;
12
+
13
+ if (delta >= delay) {
14
+ fn.apply(this, args);
15
+ lastExec = now;
16
+ } else {
17
+ if (timer) {
18
+ clearTimeout(timer);
19
+ }
20
+ timer = setTimeout(() => {
21
+ fn.apply(this, args);
22
+ lastExec = Date.now();
23
+ }, delay - delta);
24
+ }
25
+ };
26
+ };
package/src/types.ts ADDED
@@ -0,0 +1,57 @@
1
+ import { SliceZone } from "@prismicio/types";
2
+
3
+ import { RequestMessage, ResponseMessage, Transaction } from "./channel";
4
+
5
+ export type ActiveSlice = {
6
+ rect: DOMRect;
7
+ sliceID: string;
8
+ variationID: string;
9
+ index: number;
10
+ };
11
+
12
+ export type SliceZoneSize = {
13
+ rect: DOMRect;
14
+ };
15
+
16
+ export enum APIRequestType {
17
+ SetActiveSlice = "setActiveSlice",
18
+ SetSliceZoneSize = "setSliceZoneSize",
19
+ }
20
+
21
+ export type APITransactions = {
22
+ [APIRequestType.SetActiveSlice]: Transaction<
23
+ RequestMessage<APIRequestType.SetActiveSlice, ActiveSlice | null>
24
+ >;
25
+ [APIRequestType.SetSliceZoneSize]: Transaction<
26
+ RequestMessage<APIRequestType.SetSliceZoneSize, SliceZoneSize | null>
27
+ >;
28
+ };
29
+
30
+ export enum ClientRequestType {
31
+ Ping = "ping",
32
+ SetSliceZone = "setSliceZone",
33
+ ScrollToSlice = "scrollToSlice",
34
+ }
35
+
36
+ export type ClientTransactions = {
37
+ [ClientRequestType.Ping]: Transaction<
38
+ RequestMessage<ClientRequestType.Ping>,
39
+ ResponseMessage<"pong">
40
+ >;
41
+
42
+ [ClientRequestType.SetSliceZone]: Transaction<
43
+ RequestMessage<ClientRequestType.SetSliceZone, SliceZone>
44
+ >;
45
+
46
+ [ClientRequestType.ScrollToSlice]: Transaction<
47
+ RequestMessage<
48
+ ClientRequestType.ScrollToSlice,
49
+ {
50
+ sliceIndex: number;
51
+ behavior?: "auto" | "smooth";
52
+ block?: "start" | "center" | "end" | "nearest";
53
+ inline?: "start" | "center" | "end" | "nearest";
54
+ }
55
+ >
56
+ >;
57
+ };