@selimssevgi/cube-design-angular 0.0.0-stage → 0.2.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/CHANGELOG.md +23 -0
- package/README.md +423 -2
- package/fesm2022/selimssevgi-cube-design-angular.mjs +2116 -0
- package/fesm2022/selimssevgi-cube-design-angular.mjs.map +1 -0
- package/package.json +35 -3
- package/tokens/dark.css +38 -0
- package/tokens/light.css +38 -0
- package/types/selimssevgi-cube-design-angular.d.ts +750 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
- `cubeHold` (`CubeHold`, `CubeHeld`, `CUBE_HOLD_MS`, `CUBE_HOLD_SLOP`): pressing and holding a piece, the gesture cubes (tiles and the cube on top) and anlat (level tiles) each wrote by hand. a long press, a right click or the menu key raise one `cubeHeld`; a pinch or a drag never do; the click after a hold is swallowed. its hold time is 520 ms (cubes'), and each app may set its own (`cubeHoldMs`, anlat's is 480). what a hold opens stays in each app.
|
|
6
|
+
- `cube-icon` (`CubeIcon`, `CubeIconName`, `CUBE_ICON_NAMES`, `cubeIconCells`): the icon on a tile of a cube the app keeps (a context cube and its parts), moved from cubes (`shared/icon.component.ts`, `app-icon`), plus Cubes' `plus` from its tiles. `<cube-icon [icon] [inUse]>`; a view's icon (`square`, `one` … `four`, `grid-4x5`, `grid-3x6`) is the cells of its own shape. its look comes from `--cube-icon-size`, `--cube-icon-stroke`, `--cube-icon-stroke-width`, `--cube-icon-cell-opacity` and `--cube-icon-in-use`, now set in both token files.
|
|
7
|
+
- the context cube's model (`lib/context-cube/`), pure typescript, no components yet. its shape: `ContextPartKey` (names, change, view, sort, move, whose, hide, and any part an app adds), `CONTEXT_PART_ORDER` and `orderParts` (cubes' rows: how it is called, how it is shown, what i do with it), `ContextTile`, `ContextPath`, `ContextPick`. the contract an app fills from its own data, `ContextCubeSource<T>` (`parts`, `level`, `pick`, sync or a promise), and its words, `ContextWords`; both through the tokens `CONTEXT_CUBE_SOURCE` and `CONTEXT_CUBE_WORDS`. the package ships no words.
|
|
8
|
+
- the levels every app shares, as pure functions: `partsLevel` (its top), `namesLevel` (every name with its day and hands, the one in use first, a plus when the app allows), `hideLevel` (shown, hidden), `viewLevel`, `sortLevel` and `sortDirectionsLevel` (the options, the one in use, two directions).
|
|
9
|
+
- `<cube-context [held] [path]>` (`CubeContext`): one level of a context cube drawn as a complete square of icon tiles, as cubes' own cube draws it (`app-tiles` with icons, `levelFor`): quiet solid outlines for open places (dashed where a plus can add), the one in use marked and, when the app gives `ContextWords.inUse`, said, the words shrinking as the square fills. a tap asks the source's `pick` and comes out as `(enter)` (a path), `(done)` (the tile) or `(out)`; `stay` draws the level again; every tile waits while a promise is answered, and a late answer for a level already left is dropped. it never routes, zooms, draws a head or says a word. `[source]` and `[words]` override the tokens for one screen. also `contextIcon`, `contextSquare`, `contextLevelFor` and the `--cube-context-*` variables in both token files.
|
|
10
|
+
- `<cube-context>` says `(tapped)` (`ContextTapped`: the tile and its element) before the source answers, so an app grows the next level out of the tapped tile, as cubes does with every zoom. found trying it in cubes.
|
|
11
|
+
- `<cube-context>`'s own classes start with `cc-` (`cc-square`, `cc-tile`, `cc-place`, `cc-icon`, `cc-t`, `cc-m`, `cc-tick`, `cc-l0`…`cc-l4`, `cc-in-use`, `cc-pale`, `cc-adds`, `cc-busy`): cubes' global `.tile .icon`, `.place` and `.square.l2 .tile` rules reached inside it (emulated encapsulation only keeps rules in, not out). found trying it in cubes.
|
|
12
|
+
- `partsLevel` draws hide as `shown` when the app gives no `now.hide.icon` (`PART_ICONS`): `cube-icon` has no `hide` icon.
|
|
13
|
+
- from cubes' `core/world/arrange.ts`: `ViewKey`, `VIEW_KEYS`, `isView`, `Grid`, `gridFor` as they are, `Direction`, and a generic `sortItems(items, by, dir)` where what is not known yet comes last both ways.
|
|
14
|
+
- `<cube-square [tiles] [view]>` (`CubeSquare`, `CubeSquareTile`): the square, the complete grid of tiles every face is drawn with, which mosaic (`app-square-tiles`), cubes (`app-tiles`) and the context cube each wrote. it places the tiles an app gives through `<ng-template cubeSquareTile let-tile let-level="level">` (also `index`, `cell`, `rowHeight`, `list`) in any view (`gridFor`), measures its own width (up to the window's height less 48 px for the square, at most `maxSide`, 880), and zooms a step when it has to hold more. open places only finish the last row (decision of 2026-11-01: three pieces in 4 × 5 are one row of four); the square keeps its n × n; one across is a list (`listRow`, 76 px). `[dashed]` dashes the open places where something could come; `[keyOf]` keeps a tile the same tile. no words, no tile drawing, no turning yet (turning rows come on top of it, off by default). also `squareLayout`, `squareLevelFor`, `squareGap`, `SquareLayout`, `SquareLevel`, `SquareRoom`, `SQUARE_MAX_SIDE`, `SQUARE_LIST_ROW`, and the `--cube-square-*` variables (`place`, `place-dashed`, `place-width`, `radius`, `gap-scale`) in both token files.
|
|
15
|
+
- `contextLevelFor` and `ContextLevel` are now the square's `squareLevelFor` and `SquareLevel`, kept under their first names; `<cube-context>` is unchanged.
|
|
16
|
+
- `turningRows(pieces, own, cols, rows)` (`TurningRows`, `TURNING_SIDES`): the layout of turning rows (selim: row rotation), moved from mosaic (`shared/cube/turning-rows.ts`, the todarch cube) and generalised from a 3 × 3 front to cols × rows, so cubes' fixed grids (4 × 5, 3 × 6) can turn once full. pure, no angular. the front stays full, the own box keeps its last place, the rest fills row one's other three sides then row two's; past four sides a row, rows are added. with only `cols` it gives mosaic's answer. one fix on the way: a piece that is falsy (a `0`) now counts as something on its side. the square that draws and turns it is not in yet.
|
|
17
|
+
- `<cube-square [turning]>`: turning rows on the square, off by default (`turning`, `turnBelow` 640, `across` 3, `isOwn`, `nudgeKey`; `turningFront`, `TURNING_BELOW`, `TURNING_NUDGE_UNTIL`). on a phone, a square or fixed grid holding more than its front keeps its size and each row turns like a puzzle cube's layer, as in mosaic's todarch cube: a sideways drag settles on a side and passes empty ones, the edge of the next side and a nudge until three turns, `inert` sides not facing. new on the way in: the arrow keys on a focused row turn it and move the focus along (a keyboard reaches every piece), a second finger lets the turn go (a pinch), and reduced motion stops the nudge. `--cube-square-edge` and `--cube-square-focus` in both token files.
|
|
18
|
+
- `<cube-context>` draws its square with `<cube-square>` instead of its own, so the package keeps one square: the same layout, size, zoom and open places (dashed where a plus can add, `cs-dashed`), its tiles unchanged. its square and places are now `cs-square` and `cs-place`; `cc-square` and `cc-place` are gone, and the level classes sit on each tile (`cc-tile cc-l2`). `contextSquare` and `contextLevelFor` stay.
|
|
19
|
+
|
|
20
|
+
## 0.1.0
|
|
21
|
+
|
|
22
|
+
- `drawCube`, `sizeFor` and their types: the isometric cube of small cubes, moved from cubes (`core/drawing/cube-drawing.ts`), the same file mosaic copied.
|
|
23
|
+
- `cube-picture` (`CubePicture`): the cube picture, its look from css variables; `tokens/light.css` (cubes, anlat) and `tokens/dark.css` (mosaic) set them.
|
package/README.md
CHANGED
|
@@ -1,3 +1,424 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @selimssevgi/cube-design-angular
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
cube design in angular. cube design is a way of looking where a piece sits on several groupings at
|
|
4
|
+
once and every piece can open as a cube of its own (https://cube-design.selimssevgi.com). this
|
|
5
|
+
package holds the pieces of it that anlat, mosaic and cubes found the same answer for, so a
|
|
6
|
+
solution found in one app is used by all.
|
|
7
|
+
|
|
8
|
+
it holds only what has settled. what is still being discovered stays in each app until two apps use
|
|
9
|
+
it and its cube design decision is kept; then it moves in here.
|
|
10
|
+
|
|
11
|
+
## install
|
|
12
|
+
|
|
13
|
+
```shell
|
|
14
|
+
npm install @selimssevgi/cube-design-angular@0.2.0 --save-exact
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
pin the exact version: a new version changes nothing in an app until that app takes it in its own
|
|
18
|
+
PR. it needs angular 22.
|
|
19
|
+
|
|
20
|
+
## the cube picture
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { CubePicture } from '@selimssevgi/cube-design-angular';
|
|
24
|
+
|
|
25
|
+
@Component({
|
|
26
|
+
imports: [CubePicture],
|
|
27
|
+
template: `<cube-picture [count]="8" [colours]="colours()" [width]="96" [hint]="true" />`,
|
|
28
|
+
})
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| input | what it is |
|
|
32
|
+
| --------- | ----------------------------------------------------------------------------- |
|
|
33
|
+
| `count` | how many pieces; the cube is the smallest n × n × n that holds them (up to 7) |
|
|
34
|
+
| `colours` | the stickers' colours in order; `null` is a sticker not known yet, drawn pale |
|
|
35
|
+
| `width` | the width in pixels (64) |
|
|
36
|
+
| `hint` | while small, the bigger cube it can grow into, faint and dashed |
|
|
37
|
+
| `whole` | one colour on every sticker, as a value is drawn |
|
|
38
|
+
| `rot` | how many times it was turned; shifts the colours round |
|
|
39
|
+
|
|
40
|
+
its look comes from css variables, so each app keeps its own dialect. without any it draws in the
|
|
41
|
+
light dialect. add one of the two files to the app's global styles (`angular.json` › `styles`):
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
"styles": ["node_modules/@selimssevgi/cube-design-angular/tokens/dark.css", "src/styles.css"]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
or set the variables yourself: `--cube-top`, `--cube-left`, `--cube-right`, `--cube-edge`,
|
|
48
|
+
`--cube-edge-width`, `--cube-sticker-edge`, `--cube-unknown`, `--cube-unknown-edge`, `--cube-hint`,
|
|
49
|
+
`--cube-hint-opacity`. a dark mode is the same variables set again under
|
|
50
|
+
`@media (prefers-color-scheme: dark)`.
|
|
51
|
+
|
|
52
|
+
## the hold
|
|
53
|
+
|
|
54
|
+
pressing and holding a piece is how it is acted on (principle 31). `cubeHold` is that gesture and
|
|
55
|
+
nothing more: what the hold opens stays in the app.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { CubeHold, CubeHeld } from '@selimssevgi/cube-design-angular';
|
|
59
|
+
|
|
60
|
+
@Component({
|
|
61
|
+
imports: [CubeHold],
|
|
62
|
+
template: `<button cubeHold (cubeHeld)="own($event)" (click)="open()">…</button>`,
|
|
63
|
+
})
|
|
64
|
+
export class Tile {
|
|
65
|
+
own(held: CubeHeld) {
|
|
66
|
+
// held.via is 'press' or 'menu'; held.element is the button, for an own cube to grow out of
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| input | what it is |
|
|
72
|
+
| ------------------ | -------------------------------------------------------------------- |
|
|
73
|
+
| `cubeHoldDisabled` | no hold at all; the browser keeps its own menu (`false`) |
|
|
74
|
+
| `cubeHoldMs` | how long a press is held before it is a hold, in ms (`520`) |
|
|
75
|
+
| `cubeHoldSlop` | how far the press may move before it is a drag instead, in px (`10`) |
|
|
76
|
+
| `cubeHoldBuzz` | a short buzz, `navigator.vibrate(12)`, when it holds (`true`) |
|
|
77
|
+
|
|
78
|
+
`cubeHeld` gives `{ via: 'press' | 'menu', element, event }`.
|
|
79
|
+
|
|
80
|
+
- a press with a finger, a pen or the main mouse button that stays within the slop for the hold
|
|
81
|
+
time is a hold. a second finger (a pinch), a drag past the slop, letting go, a cancel or leaving
|
|
82
|
+
the element end the press without one.
|
|
83
|
+
- a `contextmenu` (a right click, a phone's own long press, the menu key, shift+f10) holds at once
|
|
84
|
+
and keeps the browser's menu away. when a long press already held, the phone's contextmenu after
|
|
85
|
+
it does not hold a second time.
|
|
86
|
+
- the one click that follows a hold is swallowed, so the piece does not also open. a tap still
|
|
87
|
+
opens it.
|
|
88
|
+
- it never stops or prevents the pointer events, so a swipe or a pinch on the same element keeps
|
|
89
|
+
working.
|
|
90
|
+
- on its element it sets `user-select: none` and `-webkit-touch-callout: none`, so no selection or
|
|
91
|
+
callout gets in the way; no other styles.
|
|
92
|
+
|
|
93
|
+
## the context cube's model
|
|
94
|
+
|
|
95
|
+
behind every piece sits a second cube, the app's, where it keeps how the piece is named, shown and
|
|
96
|
+
kept; pressing and holding a piece walks into it (principle 31; cubes calls it the own cube). the
|
|
97
|
+
package holds its shape, the app fills it:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import {
|
|
101
|
+
CONTEXT_CUBE_SOURCE,
|
|
102
|
+
CONTEXT_CUBE_WORDS,
|
|
103
|
+
ContextCubeSource,
|
|
104
|
+
hideLevel,
|
|
105
|
+
namesLevel,
|
|
106
|
+
partsLevel,
|
|
107
|
+
} from '@selimssevgi/cube-design-angular';
|
|
108
|
+
|
|
109
|
+
const todos: ContextCubeSource<Todo> = {
|
|
110
|
+
parts: (t) => ['names', 'hide'],
|
|
111
|
+
level: (t, path) =>
|
|
112
|
+
path.length === 0
|
|
113
|
+
? partsLevel(['names', 'hide'], words)
|
|
114
|
+
: path[0] === 'names'
|
|
115
|
+
? namesLevel(t.names, words, { canAdd: true })
|
|
116
|
+
: path[0] === 'hide'
|
|
117
|
+
? hideLevel(t.hidden, words)
|
|
118
|
+
: null,
|
|
119
|
+
pick: async (t, path, tile) =>
|
|
120
|
+
path.length === 0 ? { kind: 'enter', path: [tile.key] } : { kind: 'done' },
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
providers: [
|
|
124
|
+
{ provide: CONTEXT_CUBE_SOURCE, useValue: todos },
|
|
125
|
+
{ provide: CONTEXT_CUBE_WORDS, useValue: words }, // every title and line, in the app's language
|
|
126
|
+
];
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
the shared levels are `partsLevel`, `namesLevel`, `hideLevel`, `viewLevel`, `sortLevel` and
|
|
130
|
+
`sortDirectionsLevel`; each tile's `data` says what it stands for, so `pick` reads it from there.
|
|
131
|
+
`gridFor(view, count)` lays out a level or a cube, and `sortItems(items, by, dir)` orders items with
|
|
132
|
+
what is not known yet last both ways. `<cube-context>` draws a level (below).
|
|
133
|
+
|
|
134
|
+
## the context cube
|
|
135
|
+
|
|
136
|
+
`<cube-context [held] [path]>` draws one level of the held thing's context cube as a complete square
|
|
137
|
+
(`gridFor('square', n)`): an icon on every tile, the one in use marked, open places outlined, and
|
|
138
|
+
the words shrinking as the square fills. a tap asks the source's `pick`; its answer comes out as
|
|
139
|
+
`(enter)` with a deeper path, `(done)` with the tile used, or `(out)`; `stay` draws the level again
|
|
140
|
+
where it is. a path the source has no level for (null) is `(out)` too.
|
|
141
|
+
|
|
142
|
+
`(tapped)` comes first, with the tile and the element it is drawn in, before the source answers:
|
|
143
|
+
keep the element, and grow the next level out of it (or whatever the pick opens, such as a copy
|
|
144
|
+
made mine), as the app zooms into any other piece. cubes keeps it on its source.
|
|
145
|
+
|
|
146
|
+
it never routes, zooms, draws a head or says a word of its own. the app hosts it in its own route,
|
|
147
|
+
under its own head, and keeps the path in its url, so zooming out and the back button are the app's.
|
|
148
|
+
a whole example, a to-do app with names and hide:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
// todo-context.ts: the app's source and words
|
|
152
|
+
import {
|
|
153
|
+
CONTEXT_CUBE_SOURCE,
|
|
154
|
+
CONTEXT_CUBE_WORDS,
|
|
155
|
+
ContextCubeSource,
|
|
156
|
+
ContextWords,
|
|
157
|
+
NamesData,
|
|
158
|
+
hideLevel,
|
|
159
|
+
namesLevel,
|
|
160
|
+
partsLevel,
|
|
161
|
+
} from '@selimssevgi/cube-design-angular';
|
|
162
|
+
|
|
163
|
+
export const TODO_WORDS: ContextWords = {
|
|
164
|
+
parts: {
|
|
165
|
+
names: { title: 'names', line: 'every name it has had' },
|
|
166
|
+
hide: { title: 'hide', line: 'in sight or out of it' },
|
|
167
|
+
},
|
|
168
|
+
views: {},
|
|
169
|
+
directions: { up: { title: 'first first' }, down: { title: 'last first' } },
|
|
170
|
+
hide: {
|
|
171
|
+
shown: { title: 'shown', line: 'among the others' },
|
|
172
|
+
hidden: { title: 'hidden', line: 'folded away' },
|
|
173
|
+
},
|
|
174
|
+
names: { another: { title: 'another name' }, line: (n) => (n.on ? `since ${n.on}` : null) },
|
|
175
|
+
inUse: 'in use',
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export function todoContext(todos: TodoStore): ContextCubeSource<Todo> {
|
|
179
|
+
return {
|
|
180
|
+
parts: () => ['names', 'hide'],
|
|
181
|
+
level: (t, path) => {
|
|
182
|
+
switch (path[0]) {
|
|
183
|
+
case undefined:
|
|
184
|
+
// hide's tile shows what it holds now; without it, it shows `shown`.
|
|
185
|
+
return partsLevel(['names', 'hide'], TODO_WORDS, {
|
|
186
|
+
names: { line: t.name },
|
|
187
|
+
hide: { line: t.hidden ? 'hidden' : 'shown', icon: t.hidden ? 'hidden' : 'shown' },
|
|
188
|
+
});
|
|
189
|
+
case 'names':
|
|
190
|
+
return namesLevel(t.names, TODO_WORDS, { canAdd: true });
|
|
191
|
+
case 'hide':
|
|
192
|
+
return hideLevel(t.hidden, TODO_WORDS);
|
|
193
|
+
default:
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
pick: async (t, path, tile) => {
|
|
198
|
+
if (path.length === 0) return { kind: 'enter', path: [tile.key] };
|
|
199
|
+
if (path[0] === 'hide') {
|
|
200
|
+
await todos.hide(t.id, tile.data as boolean);
|
|
201
|
+
return { kind: 'done' };
|
|
202
|
+
}
|
|
203
|
+
const d = tile.data as NamesData;
|
|
204
|
+
if ('another' in d) return { kind: 'stay' }; // the app opens its own sheet to ask for it
|
|
205
|
+
await todos.rename(t.id, d.name.name);
|
|
206
|
+
return { kind: 'done' };
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export const TODO_CONTEXT_PROVIDERS = [
|
|
212
|
+
{ provide: CONTEXT_CUBE_SOURCE, useFactory: () => todoContext(inject(TodoStore)) },
|
|
213
|
+
{ provide: CONTEXT_CUBE_WORDS, useValue: TODO_WORDS },
|
|
214
|
+
];
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
// the route that hosts it: /todo/:id/context and /todo/:id/context/:part
|
|
219
|
+
{ path: 'todo/:id/context/:part', component: TodoContextScreen, providers: TODO_CONTEXT_PROVIDERS },
|
|
220
|
+
{ path: 'todo/:id/context', component: TodoContextScreen, providers: TODO_CONTEXT_PROVIDERS },
|
|
221
|
+
|
|
222
|
+
@Component({
|
|
223
|
+
imports: [CubeContext, AppHead],
|
|
224
|
+
template: `
|
|
225
|
+
@if (todo(); as t) {
|
|
226
|
+
<app-head [title]="t.name" (out)="back()" />
|
|
227
|
+
<cube-context
|
|
228
|
+
[held]="t"
|
|
229
|
+
[path]="path()"
|
|
230
|
+
(enter)="go($event)"
|
|
231
|
+
(out)="back()"
|
|
232
|
+
(done)="router.navigate(['/todo', t.id])"
|
|
233
|
+
/>
|
|
234
|
+
}
|
|
235
|
+
`,
|
|
236
|
+
})
|
|
237
|
+
export class TodoContextScreen {
|
|
238
|
+
readonly id = input.required<string>();
|
|
239
|
+
readonly part = input<string>();
|
|
240
|
+
protected readonly router = inject(Router);
|
|
241
|
+
protected readonly todo = inject(TodoStore).byId(this.id); // a signal
|
|
242
|
+
protected readonly path = computed(() => (this.part() ? [this.part()!] : []));
|
|
243
|
+
protected go(path: string[]) {
|
|
244
|
+
this.router.navigate(['/todo', this.id(), 'context', ...path]);
|
|
245
|
+
}
|
|
246
|
+
protected back() {
|
|
247
|
+
this.router.navigate(this.part() ? ['/todo', this.id(), 'context'] : ['/todo', this.id()]);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
```html
|
|
253
|
+
<!-- a tile of the list: a tap opens the to-do, a hold walks into its context cube -->
|
|
254
|
+
<button
|
|
255
|
+
cubeHold
|
|
256
|
+
(cubeHeld)="router.navigate(['/todo', t.id, 'context'])"
|
|
257
|
+
(click)="router.navigate(['/todo', t.id])"
|
|
258
|
+
>
|
|
259
|
+
{{ t.name }}
|
|
260
|
+
</button>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
a screen that holds a different source or other words passes `[source]` and `[words]` instead of
|
|
264
|
+
the tokens. `[maxSide]` (880) caps the square on a wide screen; on a phone it is the host's width,
|
|
265
|
+
and never taller than the window less 48px. `level` is read inside a computed, so a source that
|
|
266
|
+
reads signals redraws by itself; one that does not is read again after every pick.
|
|
267
|
+
|
|
268
|
+
the tile's icon is its `icon` when `cube-icon` draws it (`contextIcon`); `hide` draws `shown`, a
|
|
269
|
+
tile that adds draws the plus, and an icon of the app's own draws a face. its look is the
|
|
270
|
+
`--cube-context-*` variables, set in both token files: `-tile`, `-line`, `-ink`, `-quiet`,
|
|
271
|
+
`-in-use`, `-adds`, `-adds-bg`, `-adds-ink`, `-focus` and `-radius`, with the `--cube-icon-*` ones
|
|
272
|
+
for the icons. its square is `<cube-square>` (below), so it lays out, measures and zooms like every
|
|
273
|
+
other face; its open places take the context cube's line and radius. `contextSquare(count, room)`
|
|
274
|
+
and `contextLevelFor(cell)` are kept as plain functions.
|
|
275
|
+
|
|
276
|
+
its own classes all start with `cc-` (`cc-tile`, `cc-icon`, …) and the square's with `cs-`, so an
|
|
277
|
+
app's global `.tile`, `.square` or `.place` rules do not reach inside; style it through the
|
|
278
|
+
variables only.
|
|
279
|
+
|
|
280
|
+
## the square
|
|
281
|
+
|
|
282
|
+
every face of a cube is drawn as a complete square of tiles (decision 23). `<cube-square>` is that
|
|
283
|
+
square and nothing more: it places the tiles an app gives, and the app keeps drawing each tile.
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
import { CubeSquare, CubeSquareTile } from '@selimssevgi/cube-design-angular';
|
|
287
|
+
|
|
288
|
+
@Component({
|
|
289
|
+
imports: [CubeSquare, CubeSquareTile],
|
|
290
|
+
template: `
|
|
291
|
+
<cube-square [tiles]="pieces()" [view]="view()" [keyOf]="byId">
|
|
292
|
+
<ng-template [cubeSquareTile]="pieces()" let-piece let-level="level" let-cell="cell">
|
|
293
|
+
<app-piece-tile [piece]="piece" [level]="level" [picture]="cell * 0.4" />
|
|
294
|
+
</ng-template>
|
|
295
|
+
</cube-square>
|
|
296
|
+
`,
|
|
297
|
+
})
|
|
298
|
+
export class Face {
|
|
299
|
+
byId = (p: Piece) => p.id;
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
| input | what it is |
|
|
304
|
+
| --------- | ----------------------------------------------------------------------------------- |
|
|
305
|
+
| `tiles` | the tiles, in the order they are placed |
|
|
306
|
+
| `view` | a `ViewKey`: `square` (the default), `one` … `four` across, `grid-4x5`, `grid-3x6` |
|
|
307
|
+
| `maxSide` | the largest side it takes on a wide screen, in px (`880`) |
|
|
308
|
+
| `listRow` | a row's height in one across, the list, in px (`76`) |
|
|
309
|
+
| `dashed` | open places dashed: something could come here (a cube of mine, a level with a plus) |
|
|
310
|
+
| `keyOf` | what keeps a tile the same tile across changes (its place by default) |
|
|
311
|
+
| `turning` | turning rows, off by default (below) |
|
|
312
|
+
|
|
313
|
+
the template is given the tile (`let-tile`), its `index`, its `level` (0, everything, to 4, only its
|
|
314
|
+
icon or small cube; cubes' steps, `squareLevelFor`), its `cell` and `rowHeight` in px, and `list`
|
|
315
|
+
in one across. a tile fills its cell; give it `height: 100%` or `display: grid` if it is a button.
|
|
316
|
+
|
|
317
|
+
- the square view is the smallest n × n that holds the tiles, as wide as the host and at most the
|
|
318
|
+
window's height less 48 px, so it is seen whole. the other views are as long as they need and the
|
|
319
|
+
page scrolls.
|
|
320
|
+
- open places only finish the last row; a view never draws a whole empty row (decision of
|
|
321
|
+
2026-11-01): three pieces in 4 × 5 are one row of four. the square keeps its n × n.
|
|
322
|
+
- when it has to hold more it zooms out a step (not when the phone asks for less motion).
|
|
323
|
+
- it says no word and draws no tile, so each app keeps its own pictures, tones, hold and hide.
|
|
324
|
+
`squareLayout(view, count, room)` is the same layout as a pure function.
|
|
325
|
+
|
|
326
|
+
its look is css variables, set in both token files: `--cube-square-place` (an open place's
|
|
327
|
+
outline), `--cube-square-place-dashed`, `--cube-square-place-width` (1.5px), `--cube-square-radius`
|
|
328
|
+
(20px, closer as the square fills) and `--cube-square-gap-scale` (1; the gap itself follows the
|
|
329
|
+
square, 12 px for 2 × 2 down to 5 px from 5 × 5).
|
|
330
|
+
|
|
331
|
+
### turning rows on the square
|
|
332
|
+
|
|
333
|
+
```html
|
|
334
|
+
<cube-square
|
|
335
|
+
[tiles]="pieces()"
|
|
336
|
+
[turning]="rowsTurn()"
|
|
337
|
+
[isOwn]="isOwn"
|
|
338
|
+
nudgeKey="mosaic.turning-rows.turned"
|
|
339
|
+
>
|
|
340
|
+
…
|
|
341
|
+
</cube-square>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
with `turning` on, a square or a fixed grid (4 × 5, 3 × 6) holding more than its front keeps its
|
|
345
|
+
size on a phone and turns its rows instead (the layout is `turningRows`, below). each row is a band
|
|
346
|
+
of four sides; a sideways drag on a row turns it and it settles on a side, never between two,
|
|
347
|
+
passing the sides with nothing on them. the views of rows across (`one` … `four`) never turn, and a
|
|
348
|
+
screen as wide as `turnBelow` (640 px) shows the whole square.
|
|
349
|
+
|
|
350
|
+
| input | what it is |
|
|
351
|
+
| ----------- | ------------------------------------------------------------------------------------ |
|
|
352
|
+
| `turning` | off (`false`) unless the app turns it on; each app gives the person its own switch |
|
|
353
|
+
| `turnBelow` | rows turn only on a screen narrower than this, in px (`640`) |
|
|
354
|
+
| `across` | the square's front while it turns: `across` × `across` (`3`) |
|
|
355
|
+
| `isOwn` | which tile is my own box: it keeps the front's last place, always seen |
|
|
356
|
+
| `nudgeKey` | where the turns are counted on this phone (`localStorage`); none counts on this page |
|
|
357
|
+
|
|
358
|
+
- a row with more shows the edge of its next side, and until three turns were counted it nudges
|
|
359
|
+
(turns a little and back) when the square opens; not when the phone asks for less motion.
|
|
360
|
+
- the arrow keys on a focused row (or a tile in it) turn it, and the focus moves to the same place
|
|
361
|
+
on the side now facing, so a keyboard reaches every piece. the sides not facing are `inert` and
|
|
362
|
+
hidden from screen readers.
|
|
363
|
+
- only one finger turns, and only sideways: up and down is the page scrolling, and a second finger
|
|
364
|
+
is a pinch (the row goes back to where it was). a turn lets go of a `cubeHold` on its tile, and the
|
|
365
|
+
tap that ends a turn is swallowed.
|
|
366
|
+
- the edge's colour is `--cube-square-edge`, a focused row's outline `--cube-square-focus`.
|
|
367
|
+
|
|
368
|
+
## the drawing alone
|
|
369
|
+
|
|
370
|
+
`drawCube(count, colours, width, { hint, whole, rot })` returns the polygons as svg points, and
|
|
371
|
+
`sizeFor(count)` the side of the smallest cube that holds `count`. they are plain functions, for a
|
|
372
|
+
canvas or a picture of your own.
|
|
373
|
+
|
|
374
|
+
## the icons
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
import { CubeIcon } from '@selimssevgi/cube-design-angular';
|
|
378
|
+
|
|
379
|
+
@Component({
|
|
380
|
+
imports: [CubeIcon],
|
|
381
|
+
template: `<cube-icon [icon]="'sort'" [inUse]="false" />`,
|
|
382
|
+
})
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
a tile of a cube the app keeps (a context cube and its parts) holds no pieces, so it shows an icon of
|
|
386
|
+
what it is about instead of a small cube. `icon` is a `CubeIconName`: `names`, `name`, `change`,
|
|
387
|
+
`move`, `place`, `whose`, `library`, `mine`, `shown`, `hidden`, `view`, `sort`, `kept`, `face`,
|
|
388
|
+
`added`, `up`, `down`, `plus` (keeps something new beside the others), and the views `square`, `one`, `two`, `three`, `four`, `grid-4x5`,
|
|
389
|
+
`grid-3x6`, each drawn as the cells of its own shape. `inUse` marks the one in use among its
|
|
390
|
+
siblings. `CUBE_ICON_NAMES` lists them all.
|
|
391
|
+
|
|
392
|
+
it says nothing; the tile's words come from the app. its look is css variables, set in both token
|
|
393
|
+
files: `--cube-icon-size` (34px), `--cube-icon-stroke`, `--cube-icon-stroke-width`,
|
|
394
|
+
`--cube-icon-cell-opacity`, `--cube-icon-in-use`. set `--cube-icon-size` on a tile to make it
|
|
395
|
+
smaller or bigger.
|
|
396
|
+
|
|
397
|
+
## turning rows (the layout)
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
import { turningRows } from '@selimssevgi/cube-design-angular';
|
|
401
|
+
|
|
402
|
+
const { rows, sides } = turningRows(pieces, own, 4, 5);
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
turning rows (selim says row rotation) are an option of a view, first tried in mosaic's todarch
|
|
406
|
+
cube: a square holding more pieces than its front keeps its front full and turns its rows like the
|
|
407
|
+
layers of a puzzle cube. this is only the layout, a pure function with no angular: `rows[r][s][c]`
|
|
408
|
+
is the piece on side `s` of row `r` at place `c` (side 0 is the front, null an open place), and
|
|
409
|
+
`sides[r]` is how many sides of that row hold something (1: it does not turn).
|
|
410
|
+
|
|
411
|
+
the front is `cols` × `rows` (3 × 3 when only `cols` is given, 4 × 5 and 3 × 6 for fixed grids).
|
|
412
|
+
`own`, when not null, keeps the front's last place so it is always seen. the pieces past the front
|
|
413
|
+
fill row one's three other sides, then row two's, so the open places gather at the very end; when
|
|
414
|
+
four sides a row are not enough, rows are added below. `<cube-square [turning]>` draws and turns
|
|
415
|
+
it (above).
|
|
416
|
+
|
|
417
|
+
## rules this package keeps
|
|
418
|
+
|
|
419
|
+
- no words: it never says anything to a person. every line comes from the app, in the app's language.
|
|
420
|
+
- no tailwind classes: tailwind only builds the classes it finds in an app's own code, so they would
|
|
421
|
+
do nothing here. plain css and variables.
|
|
422
|
+
- the code's words (cube, piece, kind, value, placement); each app keeps its own on screen.
|
|
423
|
+
|
|
424
|
+
what changed in each version is in `CHANGELOG.md`.
|