@houstonp/rubiks-cube 1.5.2 → 2.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,17 +1,29 @@
1
1
  # Rubiks Cube Web Component
2
2
 
3
- This package is a rubiks cube web component built with threejs. Camera animation smoothing is done with the gsap package.
3
+ A Rubik’s Cube web component built with Three.js, WebGPU, and GSAP. The cube renders into a shadow‑DOM canvas and exposes a small, promise‑based API for cube moves, rotations, reset, and camera “peek” positions.
4
4
 
5
5
  ![cube](cube.png)
6
6
 
7
+ ## Installation
8
+
9
+ This package is published as `@houstonp/rubiks-cube`.
10
+
11
+ ```bash
12
+ bun add @houstonp/rubiks-cube
13
+ # or
14
+ npm install @houstonp/rubiks-cube
15
+ ```
16
+
7
17
  ## Adding the component
8
18
 
9
- You can add the component to a webpage by adding an import statement in the index.js file. And then
10
- by adding the webcomponent tag.
19
+ Register the custom element and then use the tag in your HTML.
11
20
 
12
21
  ```js
13
- //index.js
14
- import '@houstonp/rubiks-cube';
22
+ // index.js
23
+ import { RubiksCubeElement } from '@houstonp/rubiks-cube';
24
+
25
+ // Registers <rubiks-cube> (you can pass a different tag name if you prefer)
26
+ RubiksCubeElement.register();
15
27
  ```
16
28
 
17
29
  ```html
@@ -19,84 +31,98 @@ import '@houstonp/rubiks-cube';
19
31
  <html lang="en">
20
32
  <head>
21
33
  <meta charset="utf-8" />
34
+ <title>Rubiks Cube Demo</title>
22
35
  </head>
23
36
  <body>
24
- <rubiks-cube animation-speed-ms="1000" animation-style="exponential" piece-gap="1.04" camera-speed="100"> </rubiks-cube>
37
+ <rubiks-cube animation-speed-ms="1000" animation-style="exponential" piece-gap="1.04" camera-speed-ms="100"></rubiks-cube>
38
+
25
39
  <script type="module" src="index.js"></script>
26
40
  </body>
27
41
  </html>
28
42
  ```
29
43
 
30
- ## component attributes
44
+ ## Component attributes
45
+
46
+ These attributes control animation, spacing, and camera behavior. The available attributes
47
+ can be imported so that they can be get and set easily.
48
+
49
+ ```js
50
+ import { Attributes } from '@houstonp/rubiks-cube/schema';
31
51
 
32
- | attribute | accepted values | Description |
33
- | ---------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
34
- | animation-speed-ms | integer greater than or equal to 0 | sets the duration of the animations in milliseconds |
35
- | animation-style | "exponetial", "next", "fixed", "match" | fixed: fixed animation lengths, next: skips to next animation, exponential: speeds up successive animations, match: matches the speed the frequency of events |
36
- | piece-gap | greater than 1 | sets the gap between rubiks cube pieces |
37
- | camera-speed-ms | greater than or equal to 0 | sets the duration of camera animations in milliseconds |
38
- | camera-radius | greater than or equal to 4 | sets the camera radius |
39
- | camera-peek-angle-horizontal | decimal between 0 and 1 | sets the horizontal peek angle |
40
- | camera-peek-angle-vertical | decimal between 0 and 1 | sets the vertical peek angle |
41
- | camera-field-of-view | integer between 40 and 100 | sets the fielf of view of the camera |
52
+ const cube = document.querySelector('rubiks-cube');
53
+ const animationSpeed = cube.getAttribute(AttributeNames.animationSpeed);
54
+ cube.getAttribute(AttributeNames.animationSpeed, animationSpeed + 1);
55
+ ```
42
56
 
43
- ## state of the component
57
+ | attribute | accepted values | Description |
58
+ | ---------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
59
+ | animation-speed-ms | integer greater than or equal to 0 | Sets the duration of cube animations in milliseconds |
60
+ | animation-style | `"exponential"`, `"next"`, `"fixed"`, `"match"` | `fixed`: fixed animation lengths, `next`: skips to next animation, `exponential`: speeds up successive animations, `match`: matches the speed to the frequency of events |
61
+ | piece-gap | greater than 1 | Sets the gap between Rubik’s Cube pieces |
62
+ | camera-speed-ms | greater than or equal to 0 | Sets the duration of camera animations in milliseconds |
63
+ | camera-radius | greater than or equal to 4 | Sets the camera radius |
64
+ | camera-peek-angle-horizontal | decimal between 0 and 1 | Sets the horizontal peek angle |
65
+ | camera-peek-angle-vertical | decimal between 0 and 1 | Sets the vertical peek angle |
66
+ | camera-field-of-view | integer between 40 and 100 | Sets the field of view of the camera |
44
67
 
45
- A state event occurs when a movement animation is completed. The event details contains the current state of the cube along with the eventId of the animation. The state is an object containing the stickers of each face. A sticker is either "up", "down", "left", "right", "front" or "back".
68
+ ## Programmatic control
46
69
 
47
- To listen for the state event, add an event listener to the rubiks-cube element.
70
+ The `RubiksCubeElement` instance exposes async methods that return the cube state after the operation completes:
48
71
 
49
72
  ```js
73
+ import { RubiksCubeElement } from '@houstonp/rubiks-cube';
50
74
  const cube = document.querySelector('rubiks-cube');
51
- cube.addEventListener('state', (e) => {
52
- console.log(e.detail);
75
+
76
+ // Reset the cube; resolves with the new state string
77
+ const stateAfterReset = await cube.reset();
78
+
79
+ // Perform a move (see “Rubiks Cube Notation” below for allowed moves)
80
+ // The concrete Movement/Rotation types are exported from the package types.
81
+ cube.move(move).then((state) => {
82
+ console.log('state after move:', state);
53
83
  });
54
- /*
55
- {
56
- eventId: "guid-guid-guid-guid-guid",
57
- state: {
58
- up: [
59
- [sticker, sticker, sticker],
60
- [sticker, sticker, sticker],
61
- [sticker, sticker, sticker],
62
- ],
63
- down: [
64
- [sticker, sticker, sticker],
65
- [sticker, sticker, sticker],
66
- [sticker, sticker, sticker],
67
- ],
68
- left: [
69
- [sticker, sticker, sticker],
70
- [sticker, sticker, sticker],
71
- [sticker, sticker, sticker],
72
- ],
73
- right: [
74
- [sticker, sticker, sticker],
75
- [sticker, sticker, sticker],
76
- [sticker, sticker, sticker],
77
- ],
78
- front: [
79
- [sticker, sticker, sticker],
80
- [sticker, sticker, sticker],
81
- [sticker, sticker, sticker],
82
- ],
83
- back: [
84
- [sticker, sticker, sticker],
85
- [sticker, sticker, sticker],
86
- [sticker, sticker, sticker],
87
- ],
88
- }
89
- }
90
- */
84
+ ```
85
+
86
+ Available methods:
87
+
88
+ - `cube.move(move)` – performs a cube movement and resolves with the new state string.
89
+ - `cube.rotate(rotation)` – rotates the entire cube and resolves with the new state string.
90
+ - `cube.reset()` – resets the cube to the solved state and resolves with the new state string.
91
+ - `cube.peek(peekType)` – animates the camera to a new “peek” position and resolves with the new peek state.
92
+
93
+ All methods time out and reject if the underlying animation does not complete within an expected window.
94
+
95
+ ## Camera Actions
96
+
97
+ The camera position can be changed with the peek method available on the component.
98
+
99
+ ```js
100
+ import { RubiksCubeElement } from '@houstonp/rubiks-cube';
101
+ import { Rotations, Movements, PeekTypes, PeekState } from '@houstonp/rubiks-cube/core';
102
+
103
+ const cube = document.querySelector('rubiks-cube');
104
+ cube.peek(PeekTypes.right);
105
+ cube.peek(PeekState.rightUp);
91
106
  ```
92
107
 
93
108
  ## Rubiks Cube Notation
94
109
 
95
- Notations can include the number of roations of a face. For example, `U2` means rotate the upper face 180 degrees.
110
+ Notations can include the number of rotations of a face. For example, `U2` means rotate the upper face 180 degrees.
96
111
 
97
- Noations can also include a prime symbol `'` to indicate a counter clockwise rotation. For example, `U'` means rotate the upper face counter clockwise. The direction is always determined relative to the face being moved.
112
+ Notations can also include a prime symbol `'` to indicate a counterclockwise rotation. For example, `U'` means rotate the upper face counterclockwise. The direction is always determined relative to the face being moved.
98
113
 
99
- When both a number and a prime symbol are included the number is stated before the prime symbol. For example, `U2'` means rotate the upper face 180 degrees counter clockwise. and `U'2` is invalid.
114
+ When both a number and a prime symbol are included, the number is stated before the prime symbol. For example, `U2'` means rotate the upper face 180 degrees counterclockwise, and `U'2` is invalid.
115
+
116
+ Valid Notation is available via an export called core.
117
+
118
+ ```js
119
+ import { RubiksCubeElement } from '@houstonp/rubiks-cube';
120
+ import { Rotations, Movements, PeekTypes, PeekState } from '@houstonp/rubiks-cube/core';
121
+
122
+ const cube = document.querySelector('rubiks-cube');
123
+ cube.move(Movements.R);
124
+ cube.rotation(Rotations.x2);
125
+ ```
100
126
 
101
127
  | Notation | Movement |
102
128
  | -------- | ------------------------------------------------ |
@@ -119,142 +145,22 @@ When both a number and a prime symbol are included the number is stated before t
119
145
  | y | Rotate cube on y axis clockwise (direction of U) |
120
146
  | z | Rotate cube on z axis clockwise (direction of F) |
121
147
 
122
- these symbols are used as actionIds for the action event below.
123
-
124
- ## Updating the component
125
-
126
- The Rubiks cube web component listens for custom events to perform twists, rotations and camera changes. As per convention, the starting rotation has green facing forward, white facing up and red facing to the right.
148
+ These symbols align with the movements and rotations accepted by the component’s API.
127
149
 
128
- ### Reset event
150
+ ## Development
129
151
 
130
- The rubiks-cube element listens for the `reset` custom event and resets the cube to its initial state.
152
+ This repository is set up as an npm package and uses **Bun** for scripts and type generation.
131
153
 
132
- #### Example
154
+ - **Install dependencies**
133
155
 
134
- ```js
135
- const cube = document.querySelector('rubiks-cube');
136
- cube.dispatchEvent(new CustomEvent('reset'));
137
- ```
156
+ ```bash
157
+ bun install
158
+ ```
138
159
 
139
- ### Action event
160
+ - **Generate TypeScript declaration files**
140
161
 
141
- The rubiks-cube element listens for the `action` custom event and moves the camera to the specified position.
162
+ ```bash
163
+ bun run build:types
164
+ ```
142
165
 
143
- ```js
144
- {
145
- eventId: string,
146
- action: {
147
- type: "movement" | "camera" | "rotation",
148
- actionId: string
149
- }
150
- }
151
- ```
152
-
153
- ```js
154
- var event = new CustomEvent('action', {
155
- detail: { eventId: 'guid-guid-guid-guid-guid', action: { type: 'camera', actionId: 'peek-toggle-horizontal' } },
156
- });
157
- ```
158
-
159
- #### Camera action event
160
-
161
- action IDs for camera actions are as follows
162
-
163
- - `peek-right` - Camera is moved to the right of the cube so that the right face is visible
164
- - `peek-left` - Camera is moved to the left of the cube so that the left face is visible
165
- - `peek-top` - Camera is moved above the cube so that the top face is visible
166
- - `peek-bottom` - Camera is moved below the cube so that the bottom face is visible
167
- - `peek-toggle-horizontal` - Camera is moved to the opposite side of the cube in the horizontal plane
168
- - `peek-toggle-vertical` - Camera is moved to the opposite side of the cube in the vertical plane
169
-
170
- #### Example
171
-
172
- ```js
173
- const cube = document.querySelector('rubiks-cube');
174
- const event = new CustomEvent('action', {
175
- detail: { eventId: 'guid-guid-guid-guid-guid', action: { type: 'camera', actionId: 'peek-toggle-horizontal' } },
176
- });
177
- cube.dispatchEvent(event);
178
- ```
179
-
180
- #### Rotation action event
181
-
182
- | Notation | Rotation |
183
- | -------- | ------------------------------------------------ |
184
- | x | Rotate cube on x axis clockwise (direction of R) |
185
- | y | Rotate cube on y axis clockwise (direction of U) |
186
- | z | Rotate cube on z axis clockwise (direction of F) |
187
-
188
- actionIDs for action type "rotation" are as follows
189
-
190
- - 'x',
191
- - 'x2',
192
- - "x'",
193
- - 'y',
194
- - 'y2',
195
- - "y'",
196
- - 'z',
197
- - 'z2',
198
- - "z'",
199
-
200
- #### Example
201
-
202
- ```js
203
- const cube = document.querySelector('rubiks-cube');
204
- const event = new CustomEvent('action', {
205
- detail: { eventId: 'guid-guid-guid-guid-guid', action: { type: 'rotation', actionId: 'x' } },
206
- });
207
- cube.dispatchEvent(event);
208
- ```
209
-
210
- #### Movement action event
211
-
212
- | Notation | Movement |
213
- | -------- | ------------------------------------------ |
214
- | U | Top face clockwise |
215
- | u | Top two layers clockwise |
216
- | D | Bottom face clockwise |
217
- | d | Bottom two layers clockwise |
218
- | L | Left face clockwise |
219
- | l | Left two layers clockwise |
220
- | R | Right face clockwise |
221
- | r | Right two layers clockwise |
222
- | F | Front face clockwise |
223
- | f | Front two layers clockwise |
224
- | B | Back face clockwise |
225
- | b | Back two layers clockwise |
226
- | M | Middle layer clockwise (relative to L) |
227
- | E | Equatorial layer clockwise (relative to D) |
228
- | S | Standing layer clockwise (relative to F) |
229
-
230
- actionIDs for action type "movement" are as follows
231
-
232
- - 'R',
233
- - 'R2',
234
- - "R'",
235
- - 'L',
236
- - 'L2',
237
- - "L'",
238
- - 'U',
239
- - 'U2',
240
- - "U'",
241
- - 'D',
242
- - 'D2',
243
- - "D'",
244
- - 'F',
245
- - 'F2',
246
- - "F'",
247
- - 'B',
248
- - 'B2',
249
- - "B'",
250
- - etc...
251
-
252
- #### Example
253
-
254
- ```js
255
- const cube = document.querySelector('rubiks-cube');
256
- const event = new CustomEvent('action', {
257
- detail: { eventId: 'guid-guid-guid-guid-guid', action: { type: 'movement', actionId: 'U' } },
258
- });
259
- cube.dispatchEvent(event);
260
- ```
166
+ The generated `.d.ts` files are emitted into the `types/` directory (ignored in git) and are used for consumers of the package. There is currently no dedicated demo app or automated test suite in this repository; you can import the component into your own app (e.g., Vite, Next.js, or any ES‑module‑aware bundler) to experiment locally.
package/package.json CHANGED
@@ -1,10 +1,36 @@
1
1
  {
2
2
  "name": "@houstonp/rubiks-cube",
3
- "version": "1.5.2",
3
+ "version": "2.0.0",
4
4
  "description": "Rubiks Cube Web Component built with threejs",
5
- "main": "index.js",
6
5
  "author": "Houston Pearse",
7
6
  "license": "MIT",
7
+ "keywords": [
8
+ "rubik's cube",
9
+ "twisty puzzle",
10
+ "3x3",
11
+ "renderer",
12
+ "threejs"
13
+ ],
14
+ "main": "./src/index.js",
15
+ "types": "./types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./types/index.d.ts",
19
+ "default": "./src/index.js"
20
+ },
21
+ "./schema": {
22
+ "types": "./types/schema.d.ts",
23
+ "default": "./src/schema.js"
24
+ },
25
+ "./core": {
26
+ "types": "./types/core.d.ts",
27
+ "default": "./src/core.js"
28
+ }
29
+ },
30
+ "scripts": {
31
+ "build:types": "tsc",
32
+ "watch:types": "tsc --watch"
33
+ },
8
34
  "dependencies": {
9
35
  "gsap": "^3.14.2",
10
36
  "three": "^0.182.0"
@@ -16,5 +42,9 @@
16
42
  "bugs": {
17
43
  "url": "https://github.com/houstonpearse/rubiks-cube/issues"
18
44
  },
19
- "homepage": "https://github.com/houstonpearse/rubiks-cube#readme"
45
+ "homepage": "https://github.com/houstonpearse/rubiks-cube#readme",
46
+ "devDependencies": {
47
+ "@types/three": "^0.182.0",
48
+ "typescript": "^5.9.3"
49
+ }
20
50
  }
@@ -0,0 +1,81 @@
1
+ // @ts-check
2
+ import { PeekStates, PeekTypes } from './core';
3
+
4
+ export class CameraState {
5
+ /**
6
+ * @param {boolean} up
7
+ * @param {boolean} right
8
+ */
9
+ constructor(up = true, right = true) {
10
+ /** @type {boolean} */
11
+ this.Up = up;
12
+ /** @type {boolean} */
13
+ this.Right = right;
14
+ }
15
+
16
+ /**
17
+ * @param {import("./core").PeekType} peekType
18
+ */
19
+ peekCamera(peekType) {
20
+ switch (peekType) {
21
+ case PeekTypes.Horizontal:
22
+ this.Right = !this.Right;
23
+ break;
24
+ case PeekTypes.Vertical:
25
+ this.Up = !this.Up;
26
+ break;
27
+ case PeekTypes.Right:
28
+ this.Right = true;
29
+ break;
30
+ case PeekTypes.Left:
31
+ this.Right = false;
32
+ break;
33
+ case PeekTypes.Up:
34
+ this.Up = true;
35
+ break;
36
+ case PeekTypes.Down:
37
+ this.Up = false;
38
+ break;
39
+ case PeekTypes.RightUp:
40
+ this.Right = true;
41
+ this.Up = true;
42
+ break;
43
+ case PeekTypes.RightDown:
44
+ this.Right = true;
45
+ this.Up = false;
46
+ break;
47
+ case PeekTypes.LeftUp:
48
+ this.Right = false;
49
+ this.Up = true;
50
+ break;
51
+ case PeekTypes.LeftDown:
52
+ this.Right = false;
53
+ this.Up = false;
54
+ break;
55
+ default:
56
+ console.error(`Invalid peekType:[${peekType}]. valid values are [${Object.values(PeekTypes)}] `);
57
+ break;
58
+ }
59
+ }
60
+ /**
61
+ * @returns {import("./core").PeekState}
62
+ */
63
+ toPeekState() {
64
+ if (this.Right && this.Up) {
65
+ return PeekStates.RightUp;
66
+ }
67
+ if (!this.Right && this.Up) {
68
+ return PeekStates.LeftUp;
69
+ }
70
+ if (this.Right && !this.Up) {
71
+ return PeekStates.RightDown;
72
+ }
73
+ if (!this.Right && !this.Up) {
74
+ return PeekStates.LeftDown;
75
+ }
76
+ console.error(
77
+ `Invalid CameraState right and up values must be true or false. Actual values: right[${this.Right}] up[${this.Up}]. Default RightUp returned`,
78
+ );
79
+ return PeekStates.RightUp;
80
+ }
81
+ }
package/src/core.js ADDED
@@ -0,0 +1,127 @@
1
+ // @ts-check
2
+ /**
3
+ * @typedef {typeof Movements[keyof typeof Movements]} Movement
4
+ */
5
+ export const Movements = Object.freeze({
6
+ R: 'R',
7
+ R2: 'R2',
8
+ RP: "R'",
9
+ L: 'L',
10
+ L2: 'L2',
11
+ LP: "L'",
12
+ U: 'U',
13
+ U2: 'U2',
14
+ UP: "U'",
15
+ D: 'D',
16
+ D2: 'D2',
17
+ DP: "D'",
18
+ F: 'F',
19
+ F2: 'F2',
20
+ FP: "F'",
21
+ B: 'B',
22
+ B2: 'B2',
23
+ BP: "B'",
24
+ // Wide moves
25
+ r: 'r',
26
+ r2: 'r2',
27
+ rP: "r'",
28
+ l: 'l',
29
+ l2: 'l2',
30
+ lP: "l'",
31
+ u: 'u',
32
+ u2: 'u2',
33
+ uP: "u'",
34
+ d: 'd',
35
+ d2: 'd2',
36
+ dP: "d'",
37
+ f: 'f',
38
+ f2: 'f2',
39
+ fP: "f'",
40
+ b: 'b',
41
+ b2: 'b2',
42
+ bP: "b'",
43
+ // Slice moves
44
+ M: 'M',
45
+ M2: 'M2',
46
+ MP: "M'",
47
+ E: 'E',
48
+ E2: 'E2',
49
+ EP: "E'",
50
+ S: 'S',
51
+ S2: 'S2',
52
+ SP: "S'",
53
+ });
54
+
55
+ /**
56
+ * @typedef {typeof Rotations[keyof typeof Rotations]} Rotation
57
+ */
58
+ export const Rotations = Object.freeze({
59
+ x: 'x',
60
+ x2: 'x2',
61
+ xP: "x'",
62
+ y: 'y',
63
+ y2: 'y2',
64
+ yP: "y'",
65
+ z: 'z',
66
+ z2: 'z2',
67
+ zP: "z'",
68
+ });
69
+
70
+ /**
71
+ * @typedef {typeof Axi[keyof typeof Axi]} Axis
72
+ */
73
+ export const Axi = Object.freeze({
74
+ x: 'x',
75
+ y: 'y',
76
+ z: 'z',
77
+ });
78
+
79
+ /**
80
+ * @typedef {typeof Faces [keyof typeof Faces]} Face
81
+ */
82
+ export const Faces = Object.freeze({
83
+ up: 'U',
84
+ down: 'D',
85
+ left: 'L',
86
+ right: 'R',
87
+ front: 'F',
88
+ back: 'B',
89
+ });
90
+
91
+ /**
92
+ * @typedef {typeof FaceColours [keyof typeof FaceColours]} FaceColour
93
+ */
94
+ export const FaceColours = Object.freeze({
95
+ up: 'white',
96
+ down: 'yellow',
97
+ left: 'orange',
98
+ right: 'red',
99
+ front: 'green',
100
+ back: 'blue',
101
+ });
102
+
103
+ /**
104
+ * @typedef {typeof PeekTypes [keyof typeof PeekTypes]} PeekType
105
+ */
106
+ export const PeekTypes = Object.freeze({
107
+ Horizontal: 'horizontal',
108
+ Vertical: 'vertical',
109
+ Right: 'right',
110
+ Left: 'left',
111
+ Up: 'up',
112
+ Down: 'down',
113
+ RightUp: 'rightUp',
114
+ RightDown: 'rightDown',
115
+ LeftUp: 'leftUp',
116
+ LeftDown: 'leftDown',
117
+ });
118
+
119
+ /**
120
+ * @typedef {typeof PeekStates [keyof typeof PeekStates]} PeekState
121
+ */
122
+ export const PeekStates = Object.freeze({
123
+ RightUp: 'rightUp',
124
+ RightDown: 'rightDown',
125
+ LeftUp: 'leftUp',
126
+ LeftDown: 'leftDown',
127
+ });