@tio-canvas/canvas 0.1.1 → 0.1.3

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/client/canvas.css CHANGED
@@ -731,3 +731,17 @@ body[data-text-select],body[data-text-select] * { cursor: text !important; }
731
731
  /* The group box steps aside while screens are being reordered. */
732
732
  body[data-gesture="reorder"] #selection { border-color: transparent; }
733
733
  body[data-gesture="reorder"] #selection #handles { visibility: hidden; }
734
+
735
+ /* Change log dialog: reuses the shortcuts dialog body; one block per release.
736
+ The dialog is as tall as its content, up to the picker height. */
737
+ #changelog { height: fit-content; max-height: min(var(--canvas-picker-height),calc(100dvh - 2 * var(--canvas-panel-inset))); }
738
+ .changelog-entry + .changelog-entry { margin-top: var(--canvas-layers-heading-inset); padding-top: var(--canvas-layers-heading-inset); border-top: var(--canvas-border) solid var(--canvas-line); }
739
+ .changelog-entry h3 { display: flex; align-items: baseline; gap: var(--canvas-padding); margin: 0 0 var(--canvas-padding); font-size: inherit; font-weight: 600; color: var(--canvas-text); }
740
+ .changelog-entry time { color: var(--canvas-muted); font-weight: 400; font-variant-numeric: tabular-nums; }
741
+ .changelog-items { display: grid; grid-template-columns: 4.5rem minmax(0,1fr); column-gap: var(--canvas-gap); row-gap: var(--canvas-padding); margin: 0; padding: 0; list-style: none; }
742
+ .changelog-items li { display: grid; grid-template-columns: subgrid; grid-column: 1 / -1; align-items: baseline; text-wrap: pretty; }
743
+ .changelog-tag { --tag-hue: 265; justify-self: start; padding: 1px var(--canvas-padding); border-radius: 999px; background: oklch(0.7 0.12 var(--tag-hue) / .16); color: color-mix(in oklch, oklch(0.5 0.15 var(--tag-hue)) 70%, var(--canvas-text)); font-size: 11px; font-weight: 600; line-height: 18px; }
744
+ .changelog-tag[data-type="new"] { --tag-hue: 150; }
745
+ .changelog-tag[data-type="improved"] { --tag-hue: 250; }
746
+ .changelog-tag[data-type="fixed"] { --tag-hue: 60; }
747
+ .changelog-tag[data-type="faster"] { --tag-hue: 330; }
package/client/canvas.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import './tooltip.js';
2
2
  import './shortcuts.js';
3
+ import {renderChangelog} from './changelog.js';
3
4
  import './device-gate.js';
4
5
  import './onboarding.js';
5
6
  import { setupSelectFields, closeSelectMenus, positionSelectMenus } from './select-field.js';
@@ -921,6 +922,15 @@ shortcuts.addEventListener('click',event=>{
921
922
  if(event.clientX<rect.left || event.clientX>rect.right || event.clientY<rect.top || event.clientY>rect.bottom)shortcuts.close();
922
923
  });
923
924
  shortcuts.addEventListener('close',()=>$('#layers-more').focus());
925
+ const changelog=$('#changelog');
926
+ $('#changelog-open').onclick=()=>{closeContext();stop();clearContact();delete document.body.dataset.space;renderChangelog(changelog.querySelector('.changelog-body'));changelog.showModal();};
927
+ $('#changelog-close').onclick=()=>changelog.close();
928
+ changelog.addEventListener('click',event=>{
929
+ if(event.target!==changelog)return;
930
+ const rect=changelog.getBoundingClientRect();
931
+ if(event.clientX<rect.left || event.clientX>rect.right || event.clientY<rect.top || event.clientY>rect.bottom)changelog.close();
932
+ });
933
+ changelog.addEventListener('close',()=>$('#layers-more').focus());
924
934
  $('#undo').onclick=()=>runHistory('undo');
925
935
  $('#redo').onclick=()=>runHistory('redo');
926
936
  $('#fit').onclick=()=>fitEntries(fitAll());
@@ -962,7 +972,7 @@ moreMenu.addEventListener('beforetoggle',event=>{
962
972
  moreMenu.style.left=`${Math.max(inset,Math.min(anchor.left+(anchor.width-width)/2,innerWidth-width-inset))}px`;
963
973
  });
964
974
  moreMenu.addEventListener('toggle',event=>{if(event.newState==='open')moreMenu.focus({preventScroll:true});moreButton.setAttribute('aria-expanded',String(event.newState==='open'));});
965
- moreMenu.addEventListener('click',event=>{if(event.target.closest('#shortcuts-open,#onboarding-open'))moreMenu.hidePopover();});
975
+ moreMenu.addEventListener('click',event=>{if(event.target.closest('#shortcuts-open,#onboarding-open,#changelog-open'))moreMenu.hidePopover();});
966
976
  moreMenu.addEventListener('keydown',event=>{
967
977
  event.stopPropagation();
968
978
  if(!['ArrowDown','ArrowUp','Home','End'].includes(event.key))return;
@@ -0,0 +1,58 @@
1
+ // What changed for the person using the canvas, newest first. Only user-visible
2
+ // changes belong here: new things you can do, things that work differently,
3
+ // fixes you would have noticed, and speed. Build, release and refactor work
4
+ // never gets an entry. `type` is one of: new, improved, fixed, faster.
5
+ //
6
+ // New items go in the `unreleased` entry on top. On release,
7
+ // scripts/stamp-changelog.mjs gives it the real version and date and adds a fresh
8
+ // empty one. Keep that entry's shape.
9
+ export const entries = [
10
+ {
11
+ unreleased: true,
12
+ items: [],
13
+ },
14
+ {
15
+ version: '0.1.2',
16
+ date: '2026-11-08',
17
+ items: [
18
+ { type: 'new', text: 'A Change log in the More menu lists what changed in each release.' },
19
+ ],
20
+ },
21
+ {
22
+ version: '0.1.0',
23
+ date: '2026-11-08',
24
+ items: [
25
+ { type: 'new', text: 'The exploration canvas is now its own package, so any project can lay its pages out on an infinite board.' },
26
+ { type: 'new', text: 'A demo project ships with the canvas, so you can try it without setting anything up.' },
27
+ ],
28
+ },
29
+ ];
30
+
31
+ export const labels = { new: 'Feature', improved: 'Improved', fixed: 'Fixed', faster: 'Faster' };
32
+
33
+ const formatDate = iso => new Date(`${iso}T00:00:00Z`).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' });
34
+ const el = (tag, className, text) => { const node = document.createElement(tag); if (className) node.className = className; if (text != null) node.textContent = text; return node; };
35
+
36
+ export function renderChangelog(body, list = entries) {
37
+ body.replaceChildren();
38
+ if (!list.some(entry => entry.items.length)) { body.append(el('p', '', 'Nothing to show yet.')); return; }
39
+ for (const entry of list.filter(entry => entry.items.length)) {
40
+ const section = el('section', 'changelog-entry');
41
+ const heading = el('h3', '', entry.unreleased ? 'Unreleased' : entry.version);
42
+ if (entry.date) {
43
+ const time = el('time', '', formatDate(entry.date));
44
+ time.dateTime = entry.date;
45
+ heading.append(time);
46
+ }
47
+ const items = el('ul', 'changelog-items');
48
+ for (const item of entry.items) {
49
+ const row = el('li');
50
+ const tag = el('span', 'changelog-tag', labels[item.type] ?? item.type);
51
+ tag.dataset.type = item.type;
52
+ row.append(tag, el('span', '', item.text));
53
+ items.append(row);
54
+ }
55
+ section.append(heading, items);
56
+ body.append(section);
57
+ }
58
+ }
package/client/index.html CHANGED
@@ -90,6 +90,7 @@
90
90
  <button id="shortcuts-open" type="button" role="menuitem"><svg viewBox="0 0 20 20" aria-hidden="true"><path d="M10 5.5C8.6 4.4 6.6 4 3.5 4v11c3.1 0 5.1.4 6.5 1.5m0-11C11.4 4.4 13.4 4 16.5 4v11c-3.1 0-5.1.4-6.5 1.5m0-11v11"/></svg>Guide &amp; hotkeys</button>
91
91
  <hr role="separator">
92
92
  <button id="onboarding-open" type="button" role="menuitem"><svg viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="7"/><path d="M8.2 8a1.9 1.9 0 1 1 2.9 1.6c-.7.4-1.1.8-1.1 1.6M10 14h.01"/></svg>Onboarding</button>
93
+ <button id="changelog-open" type="button" role="menuitem"><svg viewBox="0 0 20 20" aria-hidden="true"><path d="M5.5 3h9a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1h-9a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1ZM7.5 7h5M7.5 10h5M7.5 13h3"/></svg>Change log</button>
93
94
  </div>
94
95
  <div id="screen-context" class="canvas-tools" popover="auto" role="menu" aria-label="Screen actions" tabindex="-1">
95
96
  <button type="button" role="menuitem" data-action="duplicate"><svg viewBox="0 0 20 20" aria-hidden="true"><rect x="7" y="7" width="10" height="10" rx="2"/><path d="M12 7V3H3v9h4"/></svg>Duplicate</button>
@@ -99,6 +100,10 @@
99
100
  <header class="layers-header"><h2 id="shortcuts-title">Guide &amp; hotkeys</h2><div id="shortcuts-platform" class="segmented" role="radiogroup" aria-label="Keyboard layout"><button type="button" role="radio" data-platform="mac" aria-checked="false">macOS</button><button type="button" role="radio" data-platform="win" aria-checked="false">Windows</button></div><button id="shortcuts-close" type="button" aria-label="Close Guide &amp; hotkeys" data-tooltip="Close" data-shortcut="esc">✕</button></header>
100
101
  <div class="shortcuts-body"></div>
101
102
  </dialog>
103
+ <dialog id="changelog" class="page-picker" aria-labelledby="changelog-title">
104
+ <header class="layers-header"><h2 id="changelog-title">Change log</h2><button id="changelog-close" type="button" aria-label="Close Change log" data-tooltip="Close" data-shortcut="esc">✕</button></header>
105
+ <div class="shortcuts-body changelog-body"></div>
106
+ </dialog>
102
107
  <div id="device-gate" popover="manual" role="alert">
103
108
  <div class="device-gate-icons" aria-hidden="true">
104
109
  <svg viewBox="0 0 20 20"><rect x="3" y="3" width="14" height="10" rx="1"/><path d="M10 13v4m-4 0h8"/></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tio-canvas/canvas",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "A private exploration canvas: lay your project's pages out on an infinite board, frame real documents, annotate. Development tool, plain JavaScript, ships nothing to visitors.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,6 +40,7 @@
40
40
  "node": ">=22"
41
41
  },
42
42
  "scripts": {
43
+ "dev": "node dev/serve.mjs",
43
44
  "test": "node tests/run.mjs"
44
45
  },
45
46
  "dependencies": {
@@ -51,6 +52,7 @@
51
52
  "devDependencies": {
52
53
  "@semantic-release/commit-analyzer": "^13.0.1",
53
54
  "@semantic-release/exec": "^7.1.0",
55
+ "@semantic-release/git": "^11.0.1",
54
56
  "@semantic-release/github": "^12.0.10",
55
57
  "@semantic-release/release-notes-generator": "^14.1.1",
56
58
  "semantic-release": "^24.2.9"
package/src/project.js CHANGED
@@ -20,9 +20,12 @@ export function resolveProject({ root = process.cwd(), config = DEFAULT_CONFIG,
20
20
  };
21
21
  }
22
22
 
23
+ let loadCount = 0;
24
+
23
25
  /** Import the project's config afresh and refuse a screen the canvas cannot show. */
24
26
  export async function loadProject(project) {
25
- const module = await import(`${pathToFileURL(project.configFile).href}?load=${Date.now()}`);
27
+ // A unique query per call defeats the module cache; Date.now() alone can repeat within one millisecond.
28
+ const module = await import(`${pathToFileURL(project.configFile).href}?load=${Date.now()}-${++loadCount}`);
26
29
  if (!Array.isArray(module.screens)) throw new Error(`${project.configFile} must export "screens", an array.`);
27
30
  validateScreens(module.screens);
28
31
  return { screens: module.screens, storageKey: module.storageKey ?? DEFAULT_STORAGE_KEY, presets, screenKinds };