@selimssevgi/cube-design-angular 0.1.0 → 0.2.1

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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # changelog
2
2
 
3
+ ## 0.2.1
4
+
5
+ - `<cube-context>`'s words take their size from `--cube-context-title-size` (17px) and `--cube-context-line-size` (13px, the line and the in-use word), set in both token files; the other steps keep their ratio to them. before, the sizes were fixed, so an app could not reach them through variables. found taking 0.2.0 into anlat, whose text is never under 19px. nothing changes for an app that does not set them.
6
+ - how many lines a tile's title and line show are `--cube-context-title-lines` and `--cube-context-line-lines` (2 each, as before), so an app with bigger words lets the title take a third line instead of cutting it (anlat's "adını değiştirmek istiyorum" at 19px).
7
+
8
+ ## 0.2.0
9
+
10
+ - `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.
11
+ - `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.
12
+ - 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.
13
+ - 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).
14
+ - `<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.
15
+ - `<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.
16
+ - `<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.
17
+ - `partsLevel` draws hide as `shown` when the app gives no `now.hide.icon` (`PART_ICONS`): `cube-icon` has no `hide` icon.
18
+ - 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.
19
+ - `<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.
20
+ - `contextLevelFor` and `ContextLevel` are now the square's `squareLevelFor` and `SquareLevel`, kept under their first names; `<cube-context>` is unchanged.
21
+ - `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.
22
+ - `<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.
23
+ - `<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.
24
+
3
25
  ## 0.1.0
4
26
 
5
27
  - `drawCube`, `sizeFor` and their types: the isometric cube of small cubes, moved from cubes (`core/drawing/cube-drawing.ts`), the same file mosaic copied.
package/README.md CHANGED
@@ -11,7 +11,7 @@ it and its cube design decision is kept; then it moves in here.
11
11
  ## install
12
12
 
13
13
  ```shell
14
- npm install @selimssevgi/cube-design-angular@0.1.0 --save-exact
14
+ npm install @selimssevgi/cube-design-angular@0.2.1 --save-exact
15
15
  ```
16
16
 
17
17
  pin the exact version: a new version changes nothing in an app until that app takes it in its own
@@ -49,12 +49,376 @@ or set the variables yourself: `--cube-top`, `--cube-left`, `--cube-right`, `--c
49
49
  `--cube-hint-opacity`. a dark mode is the same variables set again under
50
50
  `@media (prefers-color-scheme: dark)`.
51
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. the words' sizes are `--cube-context-title-size` (17px) and
273
+ `--cube-context-line-size` (13px, also the in-use word), the sizes on a tile of the second step
274
+ (`cc-l1`); the other steps keep their ratio to them, so an app that needs bigger letters (anlat:
275
+ nothing under 19px) sets the two and every step follows. a title and a line each show at most
276
+ `--cube-context-title-lines` and `--cube-context-line-lines` lines (2), so bigger words can take a
277
+ third line instead of being cut. its square is `<cube-square>` (below), so it lays out, measures and zooms like every
278
+ other face; its open places take the context cube's line and radius. `contextSquare(count, room)`
279
+ and `contextLevelFor(cell)` are kept as plain functions.
280
+
281
+ its own classes all start with `cc-` (`cc-tile`, `cc-icon`, …) and the square's with `cs-`, so an
282
+ app's global `.tile`, `.square` or `.place` rules do not reach inside; style it through the
283
+ variables only.
284
+
285
+ ## the square
286
+
287
+ every face of a cube is drawn as a complete square of tiles (decision 23). `<cube-square>` is that
288
+ square and nothing more: it places the tiles an app gives, and the app keeps drawing each tile.
289
+
290
+ ```ts
291
+ import { CubeSquare, CubeSquareTile } from '@selimssevgi/cube-design-angular';
292
+
293
+ @Component({
294
+ imports: [CubeSquare, CubeSquareTile],
295
+ template: `
296
+ <cube-square [tiles]="pieces()" [view]="view()" [keyOf]="byId">
297
+ <ng-template [cubeSquareTile]="pieces()" let-piece let-level="level" let-cell="cell">
298
+ <app-piece-tile [piece]="piece" [level]="level" [picture]="cell * 0.4" />
299
+ </ng-template>
300
+ </cube-square>
301
+ `,
302
+ })
303
+ export class Face {
304
+ byId = (p: Piece) => p.id;
305
+ }
306
+ ```
307
+
308
+ | input | what it is |
309
+ | --------- | ----------------------------------------------------------------------------------- |
310
+ | `tiles` | the tiles, in the order they are placed |
311
+ | `view` | a `ViewKey`: `square` (the default), `one` … `four` across, `grid-4x5`, `grid-3x6` |
312
+ | `maxSide` | the largest side it takes on a wide screen, in px (`880`) |
313
+ | `listRow` | a row's height in one across, the list, in px (`76`) |
314
+ | `dashed` | open places dashed: something could come here (a cube of mine, a level with a plus) |
315
+ | `keyOf` | what keeps a tile the same tile across changes (its place by default) |
316
+ | `turning` | turning rows, off by default (below) |
317
+
318
+ the template is given the tile (`let-tile`), its `index`, its `level` (0, everything, to 4, only its
319
+ icon or small cube; cubes' steps, `squareLevelFor`), its `cell` and `rowHeight` in px, and `list`
320
+ in one across. a tile fills its cell; give it `height: 100%` or `display: grid` if it is a button.
321
+
322
+ - the square view is the smallest n × n that holds the tiles, as wide as the host and at most the
323
+ window's height less 48 px, so it is seen whole. the other views are as long as they need and the
324
+ page scrolls.
325
+ - open places only finish the last row; a view never draws a whole empty row (decision of
326
+ 2026-11-01): three pieces in 4 × 5 are one row of four. the square keeps its n × n.
327
+ - when it has to hold more it zooms out a step (not when the phone asks for less motion).
328
+ - it says no word and draws no tile, so each app keeps its own pictures, tones, hold and hide.
329
+ `squareLayout(view, count, room)` is the same layout as a pure function.
330
+
331
+ its look is css variables, set in both token files: `--cube-square-place` (an open place's
332
+ outline), `--cube-square-place-dashed`, `--cube-square-place-width` (1.5px), `--cube-square-radius`
333
+ (20px, closer as the square fills) and `--cube-square-gap-scale` (1; the gap itself follows the
334
+ square, 12 px for 2 × 2 down to 5 px from 5 × 5).
335
+
336
+ ### turning rows on the square
337
+
338
+ ```html
339
+ <cube-square
340
+ [tiles]="pieces()"
341
+ [turning]="rowsTurn()"
342
+ [isOwn]="isOwn"
343
+ nudgeKey="mosaic.turning-rows.turned"
344
+ >
345
+ …
346
+ </cube-square>
347
+ ```
348
+
349
+ with `turning` on, a square or a fixed grid (4 × 5, 3 × 6) holding more than its front keeps its
350
+ size on a phone and turns its rows instead (the layout is `turningRows`, below). each row is a band
351
+ of four sides; a sideways drag on a row turns it and it settles on a side, never between two,
352
+ passing the sides with nothing on them. the views of rows across (`one` … `four`) never turn, and a
353
+ screen as wide as `turnBelow` (640 px) shows the whole square.
354
+
355
+ | input | what it is |
356
+ | ----------- | ------------------------------------------------------------------------------------ |
357
+ | `turning` | off (`false`) unless the app turns it on; each app gives the person its own switch |
358
+ | `turnBelow` | rows turn only on a screen narrower than this, in px (`640`) |
359
+ | `across` | the square's front while it turns: `across` × `across` (`3`) |
360
+ | `isOwn` | which tile is my own box: it keeps the front's last place, always seen |
361
+ | `nudgeKey` | where the turns are counted on this phone (`localStorage`); none counts on this page |
362
+
363
+ - a row with more shows the edge of its next side, and until three turns were counted it nudges
364
+ (turns a little and back) when the square opens; not when the phone asks for less motion.
365
+ - the arrow keys on a focused row (or a tile in it) turn it, and the focus moves to the same place
366
+ on the side now facing, so a keyboard reaches every piece. the sides not facing are `inert` and
367
+ hidden from screen readers.
368
+ - only one finger turns, and only sideways: up and down is the page scrolling, and a second finger
369
+ is a pinch (the row goes back to where it was). a turn lets go of a `cubeHold` on its tile, and the
370
+ tap that ends a turn is swallowed.
371
+ - the edge's colour is `--cube-square-edge`, a focused row's outline `--cube-square-focus`.
372
+
52
373
  ## the drawing alone
53
374
 
54
375
  `drawCube(count, colours, width, { hint, whole, rot })` returns the polygons as svg points, and
55
376
  `sizeFor(count)` the side of the smallest cube that holds `count`. they are plain functions, for a
56
377
  canvas or a picture of your own.
57
378
 
379
+ ## the icons
380
+
381
+ ```ts
382
+ import { CubeIcon } from '@selimssevgi/cube-design-angular';
383
+
384
+ @Component({
385
+ imports: [CubeIcon],
386
+ template: `<cube-icon [icon]="'sort'" [inUse]="false" />`,
387
+ })
388
+ ```
389
+
390
+ a tile of a cube the app keeps (a context cube and its parts) holds no pieces, so it shows an icon of
391
+ what it is about instead of a small cube. `icon` is a `CubeIconName`: `names`, `name`, `change`,
392
+ `move`, `place`, `whose`, `library`, `mine`, `shown`, `hidden`, `view`, `sort`, `kept`, `face`,
393
+ `added`, `up`, `down`, `plus` (keeps something new beside the others), and the views `square`, `one`, `two`, `three`, `four`, `grid-4x5`,
394
+ `grid-3x6`, each drawn as the cells of its own shape. `inUse` marks the one in use among its
395
+ siblings. `CUBE_ICON_NAMES` lists them all.
396
+
397
+ it says nothing; the tile's words come from the app. its look is css variables, set in both token
398
+ files: `--cube-icon-size` (34px), `--cube-icon-stroke`, `--cube-icon-stroke-width`,
399
+ `--cube-icon-cell-opacity`, `--cube-icon-in-use`. set `--cube-icon-size` on a tile to make it
400
+ smaller or bigger.
401
+
402
+ ## turning rows (the layout)
403
+
404
+ ```ts
405
+ import { turningRows } from '@selimssevgi/cube-design-angular';
406
+
407
+ const { rows, sides } = turningRows(pieces, own, 4, 5);
408
+ ```
409
+
410
+ turning rows (selim says row rotation) are an option of a view, first tried in mosaic's todarch
411
+ cube: a square holding more pieces than its front keeps its front full and turns its rows like the
412
+ layers of a puzzle cube. this is only the layout, a pure function with no angular: `rows[r][s][c]`
413
+ is the piece on side `s` of row `r` at place `c` (side 0 is the front, null an open place), and
414
+ `sides[r]` is how many sides of that row hold something (1: it does not turn).
415
+
416
+ the front is `cols` × `rows` (3 × 3 when only `cols` is given, 4 × 5 and 3 × 6 for fixed grids).
417
+ `own`, when not null, keeps the front's last place so it is always seen. the pieces past the front
418
+ fill row one's three other sides, then row two's, so the open places gather at the very end; when
419
+ four sides a row are not enough, rows are added below. `<cube-square [turning]>` draws and turns
420
+ it (above).
421
+
58
422
  ## rules this package keeps
59
423
 
60
424
  - no words: it never says anything to a person. every line comes from the app, in the app's language.