dsh-calendar 0.14.3 → 0.14.4

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,12 @@
1
1
  # 更新记录
2
2
 
3
+ ## 0.14.4 (2026-11-03)
4
+
5
+ - 未配置首屏复用 Empty;月历 42 格补可及名与 aria-current;空范围/失败区分;未连接按钮打开连接设置;浮层补齐共享样式。
6
+ - Unconfigured states reuse Empty, calendar cells gain accessible names and aria-current, empty ranges are distinguished from failures, disconnected actions open connection settings, and floating panels share the workbench styles.
7
+ - 同步共享 workbench-ui v1.1:Page 外的 Empty / Dialog / Status 自带样式与 `.dshw` 主题作用域,Status 新增 warn 色调。
8
+ - Sync workbench-ui v1.1: Empty / Dialog / Status now carry their own styles and `.dshw` theme scope outside Page, and Status gains a warn tone.
9
+
3
10
  ## 0.14.3 (2026-10-30)
4
11
 
5
12
  - 重整中英文首页:用途、三步流程图、安装入口与操作示例;详细配置与边界集中到完整说明。
package/lib/client.js CHANGED
@@ -494,7 +494,7 @@ function createWorkbenchUI(React) {
494
494
  }
495
495
  function Page({ title, subtitle, actions, children, workbench = false, className, rootRef, ...props }) {
496
496
  return h('section', { ...props, ref: rootRef, className: cx('dshw', 'w-page', className), 'data-main': workbench ? 'true' : 'false' },
497
- h('style', null, styles),
497
+ h(Styles, { key: 'styles' }),
498
498
  h('header', { className: 'w-page-header' }, h('div', { className: 'w-heading' }, h('h1', null, title), subtitle ? h('p', { className: 'w-subtitle' }, subtitle) : null), actions ? h('div', { className: 'w-header-actions' }, actions) : null),
499
499
  h('div', { className: 'w-body' }, children));
500
500
  }
@@ -517,10 +517,10 @@ function createWorkbenchUI(React) {
517
517
  }
518
518
  function Status({ children, tone = 'info', className }) {
519
519
  if (!children) return null;
520
- return h('div', { className: cx('w-status', 'w-status-' + tone, className), role: tone === 'error' ? 'alert' : 'status', 'aria-live': tone === 'error' ? 'assertive' : 'polite' }, h(Icon, { name: tone === 'success' ? 'check' : 'info', size: 18 }), h('div', null, children));
520
+ return h('div', { className: cx('dshw', 'w-status', 'w-status-' + tone, className), role: tone === 'error' ? 'alert' : 'status', 'aria-live': tone === 'error' ? 'assertive' : 'polite' }, h(Icon, { name: tone === 'success' ? 'check' : 'info', size: 18 }), h('div', null, children));
521
521
  }
522
522
  function Empty({ title, children, action, icon = 'file' }) {
523
- return h('div', { className: 'w-empty' }, h(Icon, { name: icon, size: 32 }), h('h2', null, title), children ? h('div', { className: 'w-muted' }, children) : null, action ? h('div', { className: 'w-toolbar' }, action) : null);
523
+ return h('div', { className: 'dshw w-empty' }, h(Styles, { key: 'styles' }), h(Icon, { name: icon, size: 32 }), h('h2', null, title), children ? h('div', { className: 'w-muted' }, children) : null, action ? h('div', { className: 'w-toolbar' }, action) : null);
524
524
  }
525
525
  function Split({ list, detail, selected, onBack, backLabel, className, listWidth = 360 }) {
526
526
  const ref = useRef(null);
@@ -564,7 +564,8 @@ function createWorkbenchUI(React) {
564
564
  else if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); }
565
565
  else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
566
566
  }
567
- return h('div', { className: 'w-overlay', onClick: event => { if (!busy && event.target === event.currentTarget) onClose(); } },
567
+ return h('div', { className: 'dshw w-overlay', onClick: event => { if (!busy && event.target === event.currentTarget) onClose(); } },
568
+ h(Styles, { key: 'styles' }),
568
569
  h('section', { ref: panel, className: cx('w-dialog', className), role: 'dialog', 'aria-modal': true, 'aria-labelledby': id, tabIndex: -1, onKeyDown: keydown },
569
570
  h('header', { className: 'w-dialog-header' }, h('h2', { id }, title), h(IconButton, { icon: 'close', label: closeLabel, disabled: busy, onClick: onClose })), h('div', { className: 'w-dialog-body' }, children)));
570
571
  }
@@ -578,7 +579,7 @@ function createWorkbenchUI(React) {
578
579
  .dshw{--w-bg:var(--dsw-alias-bg-layer-1,#fff);--w-soft:var(--dsw-alias-bg-layer-2,#f7f8fa);--w-text:var(--dsw-alias-label-primary,#18202c);--w-muted:var(--dsw-alias-label-secondary,#667085);--w-border:var(--dsw-alias-border-l3,#e7e9ef);--w-accent:var(--dsh-plugin-workbench-accent,#4f6bdb);--w-selected:color-mix(in srgb,var(--w-accent) 10%,var(--w-bg));--w-primary:var(--w-text);--w-on-primary:var(--w-bg);container-type:inline-size;container-name:dsh-workbench;color:var(--w-text);background:var(--w-bg);font:14px/1.65 var(--dsh-font-family-content,system-ui,-apple-system,"Segoe UI","Microsoft YaHei",sans-serif);min-width:0;max-width:100%;box-sizing:border-box;text-align:start;color-scheme:inherit}
579
580
  .dshw *,.dshw *:before,.dshw *:after{box-sizing:border-box}.dshw h1,.dshw h2,.dshw h3,.dshw h4,.dshw p{margin:0}.dshw h1{font-size:30px;line-height:1.25;letter-spacing:-.025em;font-weight:650}.dshw h2{font-size:24px;line-height:1.35;font-weight:650;letter-spacing:-.015em;overflow-wrap:anywhere}.dshw h3,.dshw h4{font-size:16px;line-height:1.4;font-weight:600}.dshw a{color:var(--w-accent);text-underline-offset:3px;overflow-wrap:anywhere}.dshw button,.dshw input,.dshw select,.dshw textarea{font:inherit;color:inherit;max-width:100%}.dshw input:not([type=checkbox]):not([type=radio]):not([type=file]),.dshw select,.dshw textarea{background:var(--w-bg);border:1px solid var(--w-border);border-radius:8px;padding:8px 11px;min-width:0;line-height:1.5}.dshw input:not([type=checkbox]):not([type=radio]):not([type=file]),.dshw select{min-height:38px}.dshw textarea{resize:vertical}.dshw input[type=checkbox],.dshw input[type=radio]{width:16px;height:16px;flex-shrink:0;accent-color:var(--w-accent)}.dshw :focus-visible{outline:2px solid var(--w-accent);outline-offset:3px}.dshw button:disabled,.dshw input:disabled,.dshw select:disabled,.dshw textarea:disabled{opacity:.5;cursor:not-allowed}.dshw label{font-size:13px}.dshw fieldset{min-width:0;border:1px solid var(--w-border);border-radius:8px}.dshw summary{cursor:pointer;font-weight:550;list-style:none;display:flex;align-items:center;gap:8px}.dshw summary::-webkit-details-marker{display:none}.dshw summary:after{content:'';display:block;margin-inline-start:auto;width:7px;height:7px;border-inline-end:1.75px solid currentColor;border-bottom:1.75px solid currentColor;transform:rotate(45deg);flex-shrink:0}.dshw details[open]>summary:after{transform:rotate(225deg)}.dshw details>summary+*{margin-top:12px}.dshw pre{white-space:pre-wrap;overflow-wrap:anywhere;font:12px/1.7 ui-monospace,SFMono-Regular,Consolas,monospace;max-width:100%;background:var(--w-soft);border-radius:6px;padding:14px}.dshw img,.dshw video,.dshw iframe{max-width:100%}
580
581
  .w-page{display:flex;flex-direction:column;position:relative;padding:0;min-height:540px;gap:0}.w-page[data-main=true]{height:100%;min-height:0;padding:calc(24px + var(--dsh-frame-top-clearance,0px)) 28px 0 max(28px,var(--dsh-frame-leading-clearance,0px));overflow:auto}.w-page-header{display:flex;justify-content:space-between;gap:24px;align-items:flex-start;padding-bottom:22px;flex-shrink:0}.w-heading{min-width:0}.w-subtitle{margin-top:8px!important;font-size:14px;color:var(--w-muted);line-height:1.6}.w-header-actions,.w-toolbar,.w-actions{display:flex;align-items:center;flex-wrap:wrap;gap:8px;min-width:0}.w-header-actions{justify-content:flex-end}.w-body,.w-tabs-layout{display:flex;flex-direction:column;flex:1;min-height:0;min-width:0}.w-body{overflow:auto}.w-tab-panel{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column;overflow:auto}.w-tabs{display:flex;align-items:stretch;gap:20px;border-bottom:1px solid var(--w-border);flex-shrink:0;overflow-x:auto;scrollbar-width:thin}.dshw .w-tabs>button{border:0;border-bottom:2px solid transparent;background:transparent;padding:11px 8px 13px;white-space:nowrap;font-size:14px;font-weight:550;cursor:pointer;color:var(--w-muted);display:flex;align-items:center;gap:7px}.dshw .w-tabs>button[aria-selected=true]{border-bottom-color:var(--w-accent);color:var(--w-accent)}.w-count,.w-tag{font-size:12px;line-height:1.5;display:inline-flex;align-items:center;flex-shrink:0;background:var(--w-soft);border-radius:5px;padding:1px 6px;color:var(--w-muted)}.w-tabs [aria-selected=true] .w-count,.w-selected .w-tag{color:var(--w-accent);background:var(--w-selected)}
581
- .dshw .w-button{display:inline-flex;justify-content:center;align-items:center;gap:7px;background:var(--w-bg);border:1px solid var(--w-border);border-radius:8px;padding:8px 12px;min-height:36px;line-height:1.4;font-size:14px;font-weight:500;cursor:pointer;text-decoration:none;white-space:normal}.dshw .w-button:not(:disabled):hover{background:var(--w-soft)}.dshw .w-primary{background:var(--w-primary);border-color:var(--w-primary);color:var(--w-on-primary)}.dshw .w-primary:not(:disabled):hover{background:var(--w-primary);opacity:.9}.dshw .w-ghost{background:transparent;border-color:transparent}.dshw .w-danger{color:var(--dsw-alias-state-error-primary,#b43c3c)}.dshw .w-icon-button{width:36px;min-width:36px;padding:8px}.dshw .w-button svg{flex-shrink:0}.w-muted{color:var(--w-muted);font-size:12px;line-height:1.6}.w-section{padding:24px 0;border-bottom:1px solid var(--w-border);min-width:0}.w-section>h3{margin-bottom:12px}.w-section>p+p{margin-top:8px}.w-fields{display:grid;gap:16px;min-width:0}.w-field{display:grid;gap:6px;min-width:0}.w-field>input,.w-field>select,.w-field>textarea{width:100%}.w-status{display:flex;align-items:flex-start;gap:8px;padding:11px 13px;margin:12px 0;border:1px solid var(--w-border);border-radius:8px;background:var(--w-soft);font-size:13px;overflow-wrap:anywhere;flex-shrink:0}.w-status>svg{flex-shrink:0;margin-top:2px}.w-status-error{color:var(--dsw-alias-state-error-primary,#ad3636)}.w-status-success{color:var(--dsw-alias-state-success-primary,#24794a)}.w-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:14px;max-width:580px;padding:48px 24px;margin:auto;min-height:280px;overflow-wrap:anywhere}.w-empty>svg{color:var(--w-muted)}.w-empty h2{font-size:20px}.w-empty .w-muted{font-size:14px}.w-empty .w-toolbar{justify-content:center}
582
+ .dshw .w-button{display:inline-flex;justify-content:center;align-items:center;gap:7px;background:var(--w-bg);border:1px solid var(--w-border);border-radius:8px;padding:8px 12px;min-height:36px;line-height:1.4;font-size:14px;font-weight:500;cursor:pointer;text-decoration:none;white-space:normal}.dshw .w-button:not(:disabled):hover{background:var(--w-soft)}.dshw .w-primary{background:var(--w-primary);border-color:var(--w-primary);color:var(--w-on-primary)}.dshw .w-primary:not(:disabled):hover{background:var(--w-primary);opacity:.9}.dshw .w-ghost{background:transparent;border-color:transparent}.dshw .w-danger{color:var(--dsw-alias-state-error-primary,#b43c3c)}.dshw .w-icon-button{width:36px;min-width:36px;padding:8px}.dshw .w-button svg{flex-shrink:0}.w-muted{color:var(--w-muted);font-size:12px;line-height:1.6}.w-section{padding:24px 0;border-bottom:1px solid var(--w-border);min-width:0}.w-section>h3{margin-bottom:12px}.w-section>p+p{margin-top:8px}.w-fields{display:grid;gap:16px;min-width:0}.w-field{display:grid;gap:6px;min-width:0}.w-field>input,.w-field>select,.w-field>textarea{width:100%}.w-status{display:flex;align-items:flex-start;gap:8px;padding:11px 13px;margin:12px 0;border:1px solid var(--w-border);border-radius:8px;background:var(--w-soft);font-size:13px;overflow-wrap:anywhere;flex-shrink:0}.w-status>svg{flex-shrink:0;margin-top:2px}.w-status-error{color:var(--dsw-alias-state-error-primary,#ad3636)}.w-status-success{color:var(--dsw-alias-state-success-primary,#24794a)}.w-status-warn{color:var(--dsw-alias-state-warning-primary,#8a5a00)}.w-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:14px;max-width:580px;padding:48px 24px;margin:auto;min-height:280px;overflow-wrap:anywhere}.w-empty>svg{color:var(--w-muted)}.w-empty h2{font-size:20px}.w-empty .w-muted{font-size:14px}.w-empty .w-toolbar{justify-content:center}
582
583
  .w-split{display:grid;grid-template-columns:minmax(240px,var(--w-list-width)) minmax(0,1fr);min-height:460px;flex:1;min-width:0}.w-master{min-width:0;border-inline-end:1px solid var(--w-border);overflow:auto;padding-bottom:24px}.w-detail{min-width:0;padding:26px 28px;overflow:auto;overflow-wrap:anywhere;display:flex;flex-direction:column}.w-master-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:20px 16px}.w-search{display:flex;align-items:center;gap:8px;border:1px solid var(--w-border);border-radius:8px;padding:0 10px;min-width:0;flex:1;background:var(--w-bg)}.w-search>svg{color:var(--w-muted);flex-shrink:0}.dshw .w-search>input{border:0!important;background:transparent!important;padding:8px 0!important;width:100%;outline-offset:0}.w-row{display:flex;gap:0;min-width:0;position:relative;border-bottom:1px solid var(--w-border);border-inline-start:3px solid transparent}.w-row-select{display:flex;align-items:flex-start;padding:21px 0 0 14px}.dshw .w-row-button{display:flex;flex-direction:column;align-items:stretch;gap:7px;text-align:start;width:100%;padding:18px 18px 18px 16px;border:0;background:transparent;color:inherit;min-width:0;cursor:pointer;line-height:1.5}.dshw .w-row-button:hover{background:var(--w-soft)}.w-row-head{display:flex;align-items:flex-start;gap:10px;justify-content:space-between;min-width:0}.w-row-head>strong{font-size:14px;line-height:1.5;font-weight:600;overflow-wrap:anywhere}.w-row-meta,.w-row-description{color:var(--w-muted);font-size:12px;line-height:1.6;overflow-wrap:anywhere}.w-row-description{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.w-selected{background:var(--w-selected);border-inline-start-color:var(--w-accent)}.w-back{display:none!important}.w-detail-title{margin-bottom:12px!important}.w-detail-meta{color:var(--w-muted);font-size:13px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}.w-detail-footer{padding:24px 0;display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:auto;border-top:1px solid var(--w-border)}.w-detail-footer .w-muted{flex:1;min-width:160px}.w-definition{display:grid;grid-template-columns:minmax(80px,120px) minmax(0,1fr);gap:8px 16px;font-size:14px;margin:20px 0}.w-definition dt{color:var(--w-muted)}.w-definition dd{margin:0;min-width:0;overflow-wrap:anywhere}
583
584
  .w-switch-label{display:inline-flex;align-items:center;gap:8px;font-size:13px;position:relative;cursor:pointer}.w-switch-label>input{position:absolute;opacity:0;inset-inline-end:0;width:36px!important;height:22px!important;margin:0;cursor:pointer}.w-switch-track{width:36px;height:22px;border-radius:12px;display:block;background:var(--w-border);border:1px solid var(--w-border);pointer-events:none;padding:2px;transition:background .15s}.w-switch-track:before{content:'';display:block;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 2px #0002;transition:transform .15s}.w-switch-label>input:checked+.w-switch-track{background:var(--w-accent);border-color:var(--w-accent)}.w-switch-label>input:checked+.w-switch-track:before{transform:translateX(14px)}.w-switch-label>input:focus-visible+.w-switch-track{outline:2px solid var(--w-accent);outline-offset:3px}.w-switch-label:has(>input:disabled){opacity:.5;cursor:not-allowed}
584
585
  .w-overlay{position:fixed;inset:0;background:#0005;display:flex;justify-content:flex-end;z-index:1000;padding:12px;overflow:auto}.w-dialog{width:min(620px,100%);max-height:100%;display:flex;flex-direction:column;min-height:0;background:var(--w-bg);color:var(--w-text);border:1px solid var(--w-border);border-radius:12px;box-shadow:0 18px 70px #0003}.w-dialog-header{display:flex;justify-content:space-between;align-items:center;gap:16px;padding:20px 24px;border-bottom:1px solid var(--w-border);flex-shrink:0}.w-dialog-header>h2{font-size:20px}.w-dialog-body{overflow:auto;padding:20px 24px;min-height:0}.w-editor-grid{display:grid;grid-template-columns:210px minmax(0,1fr) 300px;min-height:500px;flex:1}.w-editor-list{padding:20px 12px;border-inline-end:1px solid var(--w-border);min-width:0}.w-editor-canvas{background:var(--w-soft);padding:20px;min-width:0;display:flex;flex-direction:column;gap:14px}.w-editor-inspector{padding:24px;border-inline-start:1px solid var(--w-border);min-width:0;overflow:auto}.w-editor-inspector h3{margin-bottom:18px}.w-preview-empty{display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;gap:12px;aspect-ratio:16/9;min-height:180px;background:var(--w-soft);border:1px solid var(--w-border);border-radius:8px;padding:24px;color:var(--w-muted)}.w-content-pad{padding:24px 0}.w-stack{display:flex;flex-direction:column;gap:16px;min-width:0}.w-scroll-x{overflow:auto;max-width:100%}
@@ -587,7 +588,8 @@ function createWorkbenchUI(React) {
587
588
  @media(max-width:700px){.w-page[data-main=true]{padding:calc(16px + var(--dsh-frame-top-clearance,0px)) 16px 0 max(16px,var(--dsh-frame-leading-clearance,0px))}.w-overlay{padding:8px}.w-dialog{width:100%}}
588
589
  @media(prefers-reduced-motion:reduce){.dshw *,.dshw *:before{scroll-behavior:auto!important;transition:none!important;animation:none!important}}
589
590
  `;
590
- return { Icon, Button, IconButton, Page, Tabs, Status, Empty, Split, Row, Search, Switch, Dialog, register, styles };
591
+ function Styles() { return h('style', null, styles); }
592
+ return { Icon, Button, IconButton, Page, Tabs, Status, Empty, Split, Row, Search, Switch, Dialog, Styles, register, styles };
591
593
  }
592
594
 
593
595
  // Editable browser entry. Build with node scripts/build-client.mjs.
@@ -750,6 +752,7 @@ let calendarHostContext;
750
752
  ".dshc-meta{color:var(--dsw-alias-label-tertiary,#6b7280);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
751
753
  ".dshc-tag{display:inline-block;font-size:11px;padding:1px 6px;border-radius:999px;background:color-mix(in srgb,var(--dsw-alias-label-primary,#111) 7%,transparent);color:var(--dsw-alias-label-tertiary,#6b7280);margin-left:6px}",
752
754
  ".dshc-empty{padding:26px 18px;text-align:center;color:var(--dsw-alias-label-tertiary,#6b7280)}",
755
+ ".dshc-emptyhint{margin:0 0 6px;font-size:12px;line-height:1.6;color:var(--dsw-alias-label-tertiary,#6b7280)}",
753
756
  ".dshc-empty h3{color:var(--dsw-alias-label-primary,#111);margin:0 0 6px;font-size:14px}",
754
757
  ".dshc-empty code{background:var(--dsw-alias-bg-layer-2,rgba(0,0,0,.05));padding:1px 5px;border-radius:5px;font-size:12px}",
755
758
  ".dshc-alert{display:flex;gap:8px;align-items:flex-start;border-radius:10px;padding:8px 10px;font-size:12px;margin-bottom:10px;background:color-mix(in srgb,var(--dsw-alias-label-primary,#111) 5%,transparent)}",
@@ -1329,7 +1332,8 @@ let calendarHostContext;
1329
1332
  return null;
1330
1333
  return h("div", { className: "dshc-modalwrap", onMouseDown: (e) => { if (e.target === e.currentTarget)
1331
1334
  props.onClose(); } }, [
1332
- h("div", { className: "dshc-modal" }, [
1335
+ // 宿主没有官方 Modal 时的兜底:至少给出与官方版相同的对话框语义(标题就是可及名)。
1336
+ h("div", { className: "dshc-modal", role: "dialog", "aria-modal": "true", "aria-label": props.title }, [
1333
1337
  h("div", { style: { position: "relative" } }, [
1334
1338
  h("div", { className: "dshc-drawertitle" }, props.title),
1335
1339
  h("button", { type: "button", className: "dshc-btn ghost dshc-close", onClick: props.onClose, "aria-label": props.closeLabel || props.title }, "\u2715")
@@ -1504,6 +1508,9 @@ let calendarHostContext;
1504
1508
  role: "button",
1505
1509
  tabIndex: 0,
1506
1510
  className: "dshc-cell" + (day.getMonth() !== anchor.getMonth() ? " out" : "") + (key === today ? " today" : ""),
1511
+ // 读屏只会念出格子里的日期数字,不说这一格是干什么的;补上「哪天 + 新建」的完整名字。
1512
+ "aria-label": formatDay(day, { withYear: true }) + " · " + t("action.new"),
1513
+ "aria-current": key === today ? "date" : undefined,
1507
1514
  onClick: () => onCreateAt(day),
1508
1515
  onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ") {
1509
1516
  e.preventDefault();
@@ -1860,7 +1867,7 @@ let calendarHostContext;
1860
1867
  groups[index[key]].items.push(event);
1861
1868
  }
1862
1869
  if (groups.length === 0)
1863
- return h("div", { className: "dshc-empty" }, t("state.empty"));
1870
+ return h(W.Empty, { icon: "calendar", title: t("state.empty"), action: h(W.Button, { icon: "plus", variant: "primary", onClick: () => onCreateAt(new Date()) }, t("action.new")) });
1864
1871
  return h("div", { className: "dshc-agenda" }, groups.map((group) => h("div", { className: "dshc-daygroup", key: group.key }, [
1865
1872
  h("div", { className: "dshc-daylabel" }, [
1866
1873
  h("b", null, formatDay(group.day)),
@@ -2816,6 +2823,11 @@ __stardustUpdatesGuard(React, "dsh-calendar", { busy: busy || importBusy || laye
2816
2823
  notify(t("move.failed", { message: error !== null && error !== undefined && error.message ? error.message : String(error) }));
2817
2824
  }
2818
2825
  };
2826
+ /** 未连接日历时的点法:别让按钮「点了没反应」,直接打开连接设置并说明原因。 */
2827
+ const connectFirst = () => {
2828
+ setConnOpen(true);
2829
+ notify(calendarText('请先连接日历,再新建日程或查找空闲时段。', 'Connect a calendar before creating events or finding available time.'));
2830
+ };
2819
2831
  const notify = (text) => { setBanner(text); setTimeout(() => setBanner((current) => (current === text ? null : current)), 2200); };
2820
2832
  const title = view === "week" ? formatWeekTitle(anchor) : view === "agenda" ? t("view.agenda") : formatMonthTitle(anchor);
2821
2833
  const step = (delta) => setAnchor((current) => (view === "week" ? addDays(current, delta * 7) : addMonthShift(current, delta)));
@@ -2887,25 +2899,31 @@ __stardustUpdatesGuard(React, "dsh-calendar", { busy: busy || importBusy || laye
2887
2899
  h('div',{className:'calendar-view-controls',role:'group','aria-label':calendarText('日历视图','Calendar view')},VIEWS.map(name=>h(W.Button,{key:name,'aria-pressed':view === name,onClick:()=>{setView(name);storeView(name);}},t('view.'+name))),demo?h(W.Button,{onClick:()=>setDemo(false)},t('action.exitDemo')):h(W.IconButton,{icon:'refresh',label:t('action.refresh'),onClick:reload})));;
2888
2900
  const body = () => {
2889
2901
  if (demo === false && configured === false) {
2890
- return h("div", { className: "dshc-empty" }, [
2891
- h("h3", null, t("state.notConfigured")),
2892
- h("p", null, t("state.notConfiguredHint")),
2893
- h("div", { style: { marginTop: "12px", display: "flex", gap: "8px", justifyContent: "center" } }, [
2902
+ return h(W.Empty, {
2903
+ icon: "calendar",
2904
+ title: t("state.notConfigured"),
2905
+ action: h(React.Fragment, null, [
2894
2906
  h(Btn, { key: "cfg", variant: "primary", onClick: () => setConnOpen(true) }, t("conn.button")),
2895
2907
  h(Btn, { key: "demo", onClick: () => setDemo(true) }, t("action.demo"))
2896
2908
  ])
2897
- ]);
2909
+ }, h("p", null, t("state.notConfiguredHint")));
2898
2910
  }
2899
2911
  if (configured === null)
2900
2912
  return h("div", { className: "dshc-grid" }, Array.from({ length: 7 }, (_, i) => h("div", { className: "dshc-skel", key: i })));
2901
2913
  if (data.loading === true && data.events.length === 0) {
2902
2914
  return h("div", { className: "dshc-grid" }, Array.from({ length: 14 }, (_, i) => h("div", { className: "dshc-skel", key: i })));
2903
2915
  }
2904
- if (view === "week")
2905
- return h(WeekView, { anchor, events: visibleEvents, onPick: openEvent, onCreateAt: createAt, onReschedule: demo === true ? undefined : reschedule });
2906
- if (view === "agenda")
2907
- return h(AgendaView, { events: visibleEvents, onPick: openEvent, onCreateAt: createAt });
2908
- return h(MonthView, { anchor, events: visibleEvents, onPick: openEvent, onCreateAt: createAt });
2916
+ const viewNode = view === "week"
2917
+ ? h(WeekView, { anchor, events: visibleEvents, onPick: openEvent, onCreateAt: createAt, onReschedule: demo === true ? undefined : reschedule })
2918
+ : view === "agenda"
2919
+ ? h(AgendaView, { events: visibleEvents, onPick: openEvent, onCreateAt: createAt })
2920
+ : h(MonthView, { anchor, events: visibleEvents, onPick: openEvent, onCreateAt: createAt });
2921
+ // 空范围只给一行说明 + 下一步;出错时不冒充「没有日程」,把解释权留给错误条。
2922
+ const emptyHint = demo === false && view !== "agenda" && data.loading !== true && data.events.length === 0
2923
+ && (data.error === null || data.error === undefined);
2924
+ return emptyHint
2925
+ ? h(React.Fragment, null, h("p", { className: "dshc-emptyhint", role: "status" }, calendarText("这段时间没有日程。点日期格或「新建日程」开始。", "Nothing scheduled in this range. Click a date or use New event.")), viewNode)
2926
+ : viewNode;
2909
2927
  };
2910
2928
  const openEvent = (event) => { setConfirmDelete(false); setSelected(event); };
2911
2929
  const createAt = (day, hour) => {
@@ -2915,7 +2933,10 @@ __stardustUpdatesGuard(React, "dsh-calendar", { busy: busy || importBusy || laye
2915
2933
  }
2916
2934
  setDraft(draftAt(day, hour));
2917
2935
  };
2918
- const content = h("div", { ref: rootRef, className: "dshc-root" }, [
2936
+ // 浮层面板不是 W.Page,拿不到 ui/workbench-ui 的样式表;W.Button 之类会变成裸控件。
2937
+ // 这里补一份 kit 自己的 stylesheet(唯一规范源),不再另画一套按钮。
2938
+ const content = h("div", { ref: rootRef, className: "dshc-root" + (compact === true ? " dshw" : "") }, [
2939
+ compact === true ? h("style", { key: "kit-styles" }, W.styles) : null,
2919
2940
  h("div", { className: "dshc-shell" }, [
2920
2941
  banner !== null ? h("div", { className: "dshc-alert demo", role: "status", "aria-live": "polite" }, banner) : null,
2921
2942
  undo !== null
@@ -2994,8 +3015,8 @@ if(compact) return content;
2994
3015
  return __stardustUpdatesInlinePage(React,h(W.Page,{workbench:props.workbench===true,title:t('section.title'),subtitle:calendarText('查看日程、安排时间和管理导入。','View events, plan time and manage imports.'),className:'calendar-workbench',actions:h(React.Fragment,null,
2995
3016
  h(W.Button,{icon:'link',disabled:configured===null,onClick:()=>setConnOpen(true)},calendarText('日历连接','Calendar connection')),
2996
3017
  h(W.Button,{icon:'upload',disabled:demo || importBusy,onClick:()=>setImportOpen(value=>!value)},t('import.open')),
2997
- h(W.Button,{disabled:demo || configured!==true,onClick:()=>setAvailabilityOpen(v=>!v)},calendarText('找空闲时段','Find available time')),
2998
- h(W.Button,{variant:'primary',disabled:demo || configured!==true,title:configured!==true?calendarText('请先连接日历','Connect a calendar first'):undefined,onClick:()=>setDraft(draftAt(anchor))},t('action.new')))},h('style',null,CALENDAR_WORKBENCH_CSS),content),"dsh-calendar");
3018
+ h(W.Button,{disabled:demo || configured===null,onClick:()=>configured===true?setAvailabilityOpen(v=>!v):connectFirst()},calendarText('找空闲时段','Find available time')),
3019
+ h(W.Button,{variant:'primary',disabled:demo || configured===null,title:configured===true?undefined:calendarText('请先连接日历','Connect a calendar first'),onClick:()=>configured===true?setDraft(draftAt(anchor)):connectFirst()},t('action.new')))},h('style',null,CALENDAR_WORKBENCH_CSS),content),"dsh-calendar");
2999
3020
  }
3000
3021
  /** 月份平移:先跳到本月 1 号再加月,避免 1 月 31 日 +1 个月变成 3 月 3 日。 */
3001
3022
  function addMonthShift(date, delta) {
@@ -3125,7 +3146,7 @@ calendarHostContext=ctx;W.register(ctx,{id:'dsh-calendar.workbench',label:()=>t(
3125
3146
  exports.apply = apply;
3126
3147
  exports.inject = inject;
3127
3148
  return module.exports;
3128
- } },{"name":"dsh-calendar","version":"0.1.0","hasWorkbench":true,"packageVersion":"0.14.3","label":"日历","labelEn":"Calendar","suiteComponent":true}));
3149
+ } },{"name":"dsh-calendar","version":"0.1.0","hasWorkbench":true,"packageVersion":"0.14.4","label":"日历","labelEn":"Calendar","suiteComponent":true}));
3129
3150
 
3130
3151
  })();
3131
3152
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-calendar",
3
- "version": "0.14.3",
4
- "description": "看清日程,找到当前日历里合适的空闲时间。 需要自己的 CalDAV 账号。空闲查询只覆盖当前连接的日历。 欢迎提交 issues 和 PR。",
3
+ "version": "0.14.4",
4
+ "description": "DSH 日历工作台:CalDAV 日程、月 / 周 / 议程视图、空闲时段建议与 ICS 预览导入。欢迎提交 issues 和 PR。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
@@ -27,8 +27,8 @@
27
27
  "scripts": {
28
28
  "build": "tsc -p tsconfig.json && node scripts/build-client.mjs && node scripts/build-updates.mjs",
29
29
  "typecheck": "tsc -p tsconfig.json --noEmit",
30
- "test": "pnpm run build && node --test \"test/*.test.mjs\"",
31
- "prepublishOnly": "pnpm run build"
30
+ "test": "npm run build && node --test \"test/*.test.mjs\"",
31
+ "prepublishOnly": "npm run build"
32
32
  },
33
33
  "dsh": {
34
34
  "bundle": {