anentrypoint-design 0.0.160 โ†’ 0.0.162

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anentrypoint-design",
3
- "version": "0.0.160",
3
+ "version": "0.0.162",
4
4
  "description": "247420 design system SDK โ€” webjsx + modified ripple-ui, single-file ESM bundle for reproducible use of the AnEntrypoint design.",
5
5
  "type": "module",
6
6
  "main": "./dist/247420.js",
@@ -37,7 +37,8 @@ export function ServerRail({ servers = [], activeId, onSelect, onAdd } = {}) {
37
37
  }
38
38
 
39
39
  export function ChannelItem({ id, name, type = 'text', active, voiceActive, voiceConnecting, badge, draggable, actions = [], participants = [], onClick, onContext } = {}) {
40
- const icon = type === 'voice' ? '๐Ÿ”Š' : type === 'forum' ? 'โ—ป' : type === 'threaded' ? 'โ—‰' : type === 'announcement' ? '๐Ÿ“ฃ' : type === 'page' ? '๐Ÿ“„' : type === 'thread' ? '๐Ÿงต' : '#';
40
+ const ICON_FOR = { voice: 'speaker', forum: 'forum', threaded: 'thread', announcement: 'megaphone', page: 'page', thread: 'thread', text: 'hash' };
41
+ const icon = Icon(ICON_FOR[type] || 'hash', { size: 15 });
41
42
  const handleActionClick = (a, e) => { e.stopPropagation(); a.onClick && a.onClick(id, e); };
42
43
  return h('div', { class: 'cm-channel-item-wrap', 'data-channel-wrap': id },
43
44
  h('div', {
@@ -238,13 +239,17 @@ export function VoiceStrip({ channelName, status, muted, deafened, onMute, onDea
238
239
  );
239
240
  }
240
241
 
241
- export function MobileHeader({ title, onMenu, onMembers } = {}) {
242
+ export function MobileHeader({ title, channelType, channelName, onMenu, onMembers } = {}) {
243
+ const ICON_FOR = { voice: 'speaker', forum: 'forum', threaded: 'thread', announcement: 'megaphone', page: 'page', thread: 'thread', text: 'hash' };
244
+ const titleNode = channelType
245
+ ? [Icon(ICON_FOR[channelType] || 'hash', { size: 16 }), ' ' + (channelName || '')]
246
+ : [title || ''];
242
247
  return h('div', { class: 'cm-mobile-header', role: 'banner' },
243
248
  h('button', {
244
249
  class: 'cm-mh-btn', type: 'button', onclick: onMenu,
245
250
  title: 'Menu', 'aria-label': 'open navigation menu'
246
251
  }, Icon('menu')),
247
- h('span', { class: 'cm-mh-title' }, title || ''),
252
+ h('span', { class: 'cm-mh-title' }, ...titleNode),
248
253
  h('button', {
249
254
  class: 'cm-mh-btn', type: 'button', onclick: onMembers,
250
255
  title: 'Members', 'aria-label': 'show members'
@@ -325,15 +325,22 @@ export function ContextMenu({ items = [], anchor = { x: 0, y: 0 }, onClose } = {
325
325
  ref: (el) => {
326
326
  if (!el) return;
327
327
  rootEl = el;
328
- // Clamp to viewport
329
- const vw = window.innerWidth, vh = window.innerHeight;
330
- let x = anchor.x || 0, y = anchor.y || 0;
331
- el.style.left = '0px'; el.style.top = '0px';
332
- const r = el.getBoundingClientRect();
333
- if (x + r.width > vw) x = Math.max(0, vw - r.width - 4);
334
- if (y + r.height > vh) y = Math.max(0, vh - r.height - 4);
335
- el.style.left = x + 'px';
336
- el.style.top = y + 'px';
328
+ // Position at the anchor immediately, then clamp once layout has
329
+ // settled โ€” measuring synchronously in ref reads a zero-size box
330
+ // (children not yet painted), so the clamp must run post-layout.
331
+ const ax = anchor.x || 0, ay = anchor.y || 0;
332
+ el.style.left = ax + 'px';
333
+ el.style.top = ay + 'px';
334
+ const clamp = () => {
335
+ const vw = window.innerWidth, vh = window.innerHeight;
336
+ const r = el.getBoundingClientRect();
337
+ let x = ax, y = ay;
338
+ if (x + r.width > vw) x = Math.max(4, vw - r.width - 4);
339
+ if (y + r.height > vh) y = Math.max(4, vh - r.height - 4);
340
+ el.style.left = x + 'px';
341
+ el.style.top = y + 'px';
342
+ };
343
+ requestAnimationFrame(clamp);
337
344
  queueMicrotask(() => { el.querySelector('button[data-ix]')?.focus(); });
338
345
  }
339
346
  },
@@ -90,7 +90,12 @@ const ICON_PATHS = {
90
90
  smile: '<circle cx="12" cy="12" r="9"/><path d="M8 14a4 4 0 0 0 8 0"/><path d="M9 9h.01M15 9h.01"/>',
91
91
  'more-horizontal': '<circle cx="5" cy="12" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/>',
92
92
  'arrow-up': '<path d="M12 19V5M5 12l7-7 7 7"/>',
93
- send: '<path d="M22 2 11 13M22 2l-7 20-4-9-9-4z"/>'
93
+ send: '<path d="M22 2 11 13M22 2l-7 20-4-9-9-4z"/>',
94
+ hash: '<path d="M4 9h16M4 15h16M10 3 8 21M16 3l-2 18"/>',
95
+ megaphone: '<path d="M3 11v2a1 1 0 0 0 1 1h2l5 4V6L6 10H4a1 1 0 0 0-1 1z"/><path d="M15 8a4 4 0 0 1 0 8M18 5a8 8 0 0 1 0 14"/>',
96
+ forum: '<path d="M4 5h13a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H9l-4 3v-3H4a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1z"/>',
97
+ page: '<path d="M6 3h8l5 5v13a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z"/><path d="M14 3v5h5M8 13h8M8 17h6"/>',
98
+ thread: '<path d="M5 6h14M5 11h14M5 16h8"/><circle cx="17" cy="17" r="3"/>'
94
99
  };
95
100
  export function Icon(name, { size = 16 } = {}) {
96
101
  const inner = ICON_PATHS[name];
@@ -189,6 +194,14 @@ function toggleSide(open) {
189
194
  export function AppShell({ topbar, crumb, side, main, status, narrow } = {}) {
190
195
  const hasSide = Boolean(side);
191
196
  const sideNode = hasSide ? side : h('aside', { class: 'app-side', 'aria-hidden': 'true' });
197
+ // Topbar and crumb used to stack as two separate chrome bars โ€” a "double
198
+ // title bar". When both are present, fold them into one sticky row:
199
+ // brand + nav (topbar) and breadcrumb + right slot (crumb) share a single
200
+ // band so the chrome reads as one bar, not two. Either prop alone still
201
+ // renders on its own (consumers that pass only a topbar are unaffected).
202
+ const chrome = (topbar && crumb)
203
+ ? h('div', { class: 'app-chrome' }, topbar, crumb)
204
+ : (topbar || crumb || null);
192
205
  return h('div', { class: 'app' },
193
206
  h('a', { href: '#app-main', class: 'skip-link' }, 'skip to main content'),
194
207
  hasSide ? h('button', {
@@ -196,8 +209,7 @@ export function AppShell({ topbar, crumb, side, main, status, narrow } = {}) {
196
209
  'aria-label': 'toggle navigation', 'aria-expanded': 'false', 'aria-controls': 'app-main',
197
210
  onclick: () => toggleSide(),
198
211
  }, Icon('menu')) : null,
199
- topbar || null,
200
- crumb || null,
212
+ chrome,
201
213
  h('div', { class: 'app-body' + (hasSide ? '' : ' no-side') },
202
214
  h('div', { class: 'app-side-scrim', 'aria-hidden': 'true', onclick: () => toggleSide(false) }),
203
215
  h('div', { class: 'app-side-shell', onclick: (e) => { if (e.target.closest('a')) toggleSide(false); } }, sideNode),