@aiquants/spreadsheet 0.9.0 → 0.10.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 +54 -0
- package/README.md +74 -5
- package/dist/index.cjs +14 -14
- package/dist/index.d.cts +129 -7
- package/dist/index.d.ts +129 -7
- package/dist/index.js +4318 -4140
- package/dist/styles/spreadsheet.standalone.css +1 -1
- package/package.json +5 -5
- package/src/Spreadsheet.tsx +152 -12
- package/src/clipboard/tiling.ts +106 -16
- package/src/clipboard/useClipboard.ts +120 -7
- package/src/commands/useCommands.ts +226 -42
- package/src/editor/useCellEditor.ts +62 -13
- package/src/grid/GridBody.tsx +29 -3
- package/src/index.ts +5 -1
- package/src/keyboard/useGridKeyboard.ts +24 -3
- package/src/model/dataAdapter.ts +121 -2
- package/src/model/labels.ts +8 -2
- package/src/overlay/useFillHandle.ts +35 -2
- package/src/popover/CommentPopover.tsx +14 -2
- package/src/popover/LinkPopover.tsx +15 -3
- package/src/popover/NotePopover.tsx +24 -6
- package/src/popover/usePopovers.ts +89 -11
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
|
|
2
2
|
/*! @aiquants/spreadsheet standalone CSS — 非 Tailwind ホスト専用。ホストの Tailwind ビルドと混在させないこと */
|
|
3
|
-
@layer theme,base;@layer components{@layer components{.aqvs-scroll-pane{display:flex;position:relative}.aqvs-scroll-pane[data-self-measured=true]{height:100%}.aqvs-scroll-pane-content{box-sizing:border-box;flex:1;height:100%;position:relative;overflow:hidden}.aqvs-tap-scroll-circle{touch-action:none;-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;transition-property:transform;transition-duration:.1s;transition-timing-function:cubic-bezier(0,0,.2,1);display:flex;position:relative}.aqvs-tap-scroll-circle-visual-outer{border-radius:9999px;position:absolute;inset:0}.aqvs-tap-scroll-circle-gradient{background:linear-gradient(to bottom right,#1d4ed899,#60a5fa8c,#bfdbfe66);border:1px solid #fff6;border-radius:9999px;position:absolute;inset:0;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.aqvs-tap-scroll-circle-inner{border-radius:9999px;position:absolute;inset:18%}.aqvs-sample-visual-pupil{box-sizing:border-box;background-color:#ffffffd9;border:1px solid #ffffff80;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-sample-visual-highlight{background-color:#fffc;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-sample-visual-rod{background-color:#ffffff80;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-scrollbar{z-index:50;cursor:default;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;touch-action:none;background-color:#fff;position:relative}.aqvs-scrollbar-horizontal{flex-direction:row;align-items:stretch;display:flex}.aqvs-scrollbar-vertical{flex-direction:column;align-items:stretch;display:flex}.aqvs-scrollbar[data-visible=false],.aqvs-scrollbar-thumb-wrapper[data-visible=false]{opacity:0;pointer-events:none}.aqvs-scrollbar-tap-circle-wrapper{pointer-events:auto;transition-property:opacity;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);position:absolute}.aqvs-scrollbar-arrow-button{color:#313131;letter-spacing:normal;background-color:#e0e0e0;border:0 solid;justify-content:center;align-items:center;padding:0;font-family:system-ui,sans-serif;font-size:.75rem;font-weight:400;line-height:1rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:flex}.aqvs-scrollbar-arrow-button:focus{outline-offset:2px;outline:2px solid #0000}.aqvs-scrollbar-arrow-button:focus-visible{outline-offset:1px;outline:2px solid #60a5fa}.aqvs-scrollbar-arrow-button:disabled{cursor:not-allowed;opacity:.5}.aqvs-scrollbar-arrow-button:enabled:hover{background-color:#d4d4d4}.aqvs-scrollbar-track{background-color:#f5f5f5;flex:1;position:relative}.aqvs-scrollbar-overlay{pointer-events:none;position:absolute;inset:0}.aqvs-scrollbar-thumb-wrapper{touch-action:none;position:absolute}.aqvs-scrollbar-thumb{transform-origin:50%;background-color:#7f7f7f;transition:transform 80ms ease-out;position:absolute}.aqvs-scrollbar-thumb[data-thumb-state=disabled]{transition:none;transform:none}.aqvs-scrollbar-thumb[data-thumb-state=hover]{background-color:#5f5f5f}.aqvs-scrollbar-thumb[data-thumb-state=dragging]{background-color:#4f4f4f;transition:transform 60ms ease-out}.aqvs-scrollbar-thumb-horizontal{inset:1.5px 0}.aqvs-scrollbar-thumb-horizontal[data-thumb-state=hover]{top:-.5px;bottom:-.5px;transform:scaleY(1.06)}.aqvs-scrollbar-thumb-horizontal[data-thumb-state=dragging]{top:-.5px;bottom:-.5px;transform:scaleY(1.12)}.aqvs-scrollbar-thumb-vertical{inset:0 1.5px}.aqvs-scrollbar-thumb-vertical[data-thumb-state=hover]{left:-.5px;right:-.5px;transform:scaleX(1.06)}.aqvs-scrollbar-thumb-vertical[data-thumb-state=dragging]{left:-.5px;right:-.5px;transform:scaleX(1.12)}.aqvs-scroll-to-edge-button{pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;color:#fff;text-transform:uppercase;letter-spacing:.05em;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background-color:#1f2937cc;border:0 solid;border-radius:9999px;padding:.25rem 3rem;font-family:inherit;font-size:10px;font-weight:500;line-height:1.5;transition-property:transform,background-color;transition-duration:.15s;transform:none;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.aqvs-scroll-to-edge-button:focus{outline-offset:2px;outline:2px solid #0000}.aqvs-scroll-to-edge-button:focus-visible{outline-offset:1px;outline:2px solid #60a5fa}.aqvs-scroll-to-edge-button:hover{background-color:#374151}.aqvs-scroll-to-edge-button:active{transform:scale(.95)}.aqvs-scroll-to-edge-overlay{pointer-events:none;z-index:10;transition-property:opacity;transition-duration:.5s;position:absolute;inset:0}.aqvs-scroll-to-edge-overlay[data-visible=true]{opacity:1}.aqvs-scroll-to-edge-overlay[data-visible=false]{opacity:0}.aqvs-scroll-to-edge-overlay[data-visible=false] .aqvs-scroll-to-edge-button{pointer-events:none}.aqvs-scroll-to-edge-button-container{justify-content:center;display:flex;position:absolute;left:0;right:0}.aqvs-scroll-to-edge-button-container-top{top:.5rem}.aqvs-scroll-to-edge-button-container-bottom{bottom:.5rem}.aqvs-no-items-container{width:100%;position:absolute;top:0}.aqvs-no-items-text{text-align:center;color:#6b7280}.aqvs-item-container,.aqvs-bottom-inset,.aqvs-items-wrapper{width:100%;position:absolute}.aqvs-live-region{clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.aqvs-grid{grid-template-rows:minmax(0,1fr) auto;width:100%;height:100%;display:grid;position:relative;overflow:hidden}.aqvs-grid-has-frozen-rows{grid-template-rows:auto minmax(0,1fr) auto}.aqvs-grid-has-trailing-rows{grid-template-rows:minmax(0,1fr) auto auto}.aqvs-grid-has-frozen-rows.aqvs-grid-has-trailing-rows{grid-template-rows:auto minmax(0,1fr) auto auto}.aqvs-grid-frozen-rows{min-width:0;position:relative;overflow:hidden}.aqvs-grid-frozen-band-row{position:absolute;left:0;overflow:hidden}.aqvs-grid-trailing-rows{min-width:0;position:relative;overflow:hidden}.aqvs-grid-trailing-rows-inner{position:absolute;bottom:0;left:0;right:0}.aqvs-grid-trailing-band-row{position:absolute;left:0;overflow:hidden}.aqvs-grid-main{min-width:0;min-height:0;position:relative}.aqvs-grid-row{width:100%;height:100%;transform:translateX(var(--aqvs-grid-hx-residual,0px));position:relative;overflow:visible}.aqvs-grid-cell{height:100%;position:absolute;top:0;overflow:hidden}.aqvs-grid-row.aqvs-grid-row-frozen-host{transform:none}.aqvs-grid-row-scroll{height:100%;top:0;left:var(--aqvs-grid-frozen-width,0px);right:var(--aqvs-grid-trailing-width,0px);position:absolute;overflow:hidden}.aqvs-grid-row-scroll-inner{width:100%;height:100%;transform:translateX(calc(var(--aqvs-grid-hx-residual,0px) - var(--aqvs-grid-frozen-width,0px)));position:absolute;top:0;left:0}.aqvs-grid-row-trailing{height:100%;top:0;left:max(var(--aqvs-grid-frozen-width,0px), calc(100% - var(--aqvs-grid-trailing-width,0px)));position:absolute;right:0;overflow:hidden}.aqvs-grid-row-trailing-inner{height:100%;width:var(--aqvs-grid-trailing-width,0px);position:absolute;top:0;right:0}.aqvs-grid-hbar-strip{min-width:0;padding-left:var(--aqvs-grid-frozen-width,0px);position:relative}.aqvs-grid-live-region{clip:rect(0 0 0 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}}.aqsp-root,.aqsp-root *,.aqsp-root :before,.aqsp-root :after{box-sizing:border-box}.aqsp-root{--aqsp-row-header-width:64px;--aqsp-col-header-height:24px;--aqsp-band-row:#0084ff;--aqsp-band-column:#42ff4f;--aqsp-band-intersection:#e1ff00;--aqsp-band-opacity:.2;color:#1f2937;background:#fff;width:100%;height:100%;font-size:13px;line-height:1;position:relative;overflow:hidden}.aqsp-zoom{grid-template-columns:var(--aqsp-row-header-width) minmax(0, 1fr);grid-template-rows:var(--aqsp-col-header-height) minmax(0, 1fr);transform-origin:0 0;width:100%;height:100%;display:grid}.aqsp-corner{background:#f3f4f6;border-bottom:1px solid #d1d5db;border-right:1px solid #d1d5db;grid-area:1/1}.aqsp-col-header{-webkit-user-select:none;user-select:none;background:#f3f4f6;border-bottom:1px solid #d1d5db;grid-area:1/2;position:relative;overflow:hidden}.aqsp-col-header-scroll{height:100%;position:absolute;top:0;right:0;overflow:hidden}.aqsp-row-header-scroll{width:100%;position:absolute;bottom:0;left:0;overflow:hidden}.aqsp-col-header-track{will-change:transform;position:absolute;inset:0}.aqsp-col-header-cell{white-space:nowrap;color:#4b5563;border-right:1px solid #e5e7eb;justify-content:center;align-items:center;height:100%;display:flex;position:absolute;top:0;overflow:hidden}.aqsp-row-header{-webkit-user-select:none;user-select:none;background:#f3f4f6;border-right:1px solid #d1d5db;grid-area:2/1;position:relative;overflow:hidden}.aqsp-row-header-track{will-change:transform;position:absolute;top:0;left:0;right:0}.aqsp-row-header-cell{white-space:nowrap;color:#4b5563;font-variant-numeric:tabular-nums;border-bottom:1px solid #e5e7eb;justify-content:flex-end;align-items:center;padding-right:6px;display:flex;position:absolute;left:0;right:0;overflow:hidden}.aqsp-body{position:absolute;inset:0}.aqsp-row{cursor:default}.aqsp-cell{white-space:nowrap;background:#fff;border-bottom:1px solid #e5e7eb;border-right:1px solid #e5e7eb;align-items:center;padding:0 4px;display:flex}.aqsp-cell[data-aqsp-valign=top]{align-items:flex-start}.aqsp-cell[data-aqsp-valign=bottom]{align-items:flex-end}.aqsp-cell[data-aqsp-wrap]{white-space:pre-wrap;overflow-wrap:anywhere}.aqsp-cell[data-aqsp-note]:after,.aqsp-cell[data-aqsp-comment]:after{content:"";pointer-events:none;border:5px solid #4b5563;border-color:#4b5563 #4b5563 #0000 #0000;width:0;height:0;position:absolute;top:0;right:0}.aqsp-cell[data-aqsp-comment]:after{border-top-color:#f97316;border-right-color:#f97316}.aqsp-cell[data-aqsp-invalid]:before{content:"";pointer-events:none;border:5px solid #0000;border-color:#0000 #0000 #dc2626 #dc2626;width:0;height:0;position:absolute;bottom:0;left:0}.aqsp-cell[data-aqsp-link]{color:#1d4ed8;text-decoration:underline}.aqsp-body-stack{grid-area:2/2;position:relative}.aqsp-overlay{pointer-events:none;position:absolute;inset:0;overflow:hidden}.aqsp-overlay-quad{pointer-events:none;position:absolute;overflow:hidden}.aqsp-selection-fill{mix-blend-mode:multiply;background:#3b82f62e;border:1px solid #2563ebd9;position:absolute;top:0;left:0}.aqsp-active-frame{box-sizing:border-box;border:2px solid #2563eb;position:absolute;top:0;left:0}.aqsp-drag-rect{background:#3b82f61a;border:1px solid #2563eb;position:absolute;top:0;left:0}.aqsp-drag-rect[data-aqsp-drag-mode=subtract]{background:#dc262614;border:1px dashed #dc2626}.aqsp-drag-rect[data-aqsp-drag-mode=point]{background:#16a34a14;border:1px dashed #16a34a}.aqsp-band{opacity:var(--aqsp-band-opacity);pointer-events:none;position:absolute;top:0;left:0}.aqsp-band[data-aqsp-band=row]{background:var(--aqsp-band-row)}.aqsp-band[data-aqsp-band=column]{background:var(--aqsp-band-column)}.aqsp-band[data-aqsp-band=intersection]{background:var(--aqsp-band-intersection)}.aqsp-clip-marquee{position:absolute;top:0;left:0}.aqsp-clip-marquee:after{content:"";top:var(--aqsp-clip-outset-top,-3px);right:var(--aqsp-clip-outset-right,-3px);bottom:var(--aqsp-clip-outset-bottom,-3px);left:var(--aqsp-clip-outset-left,-3px);background-image:linear-gradient(90deg,#2563eb 50%,#fff 50%),linear-gradient(90deg,#2563eb 50%,#fff 50%),linear-gradient(#fff 50%,#2563eb 50%),linear-gradient(#fff 50%,#2563eb 50%);background-position:0 0,8px 100%,0 8px,100% 0;background-repeat:repeat-x,repeat-x,repeat-y,repeat-y;background-size:8px 2px,8px 2px,2px 8px,2px 8px;animation:.4s linear infinite aqsp-marching-ants;position:absolute}@keyframes aqsp-marching-ants{to{background-position:8px 0,0 100%,0 0,100% 8px}}@media (prefers-reduced-motion:reduce){.aqsp-clip-marquee:after{animation:none}}.aqsp-col-resize-strip{cursor:col-resize;height:100%;position:absolute;top:0}.aqsp-row-resize-strip{cursor:row-resize;position:absolute;left:0;right:0}.aqsp-col-header .aqsp-unhide-marker{cursor:col-resize;opacity:.7;background:repeating-linear-gradient(90deg,#2563eb 0 1px,#0000 1px 3px);height:100%;position:absolute;top:0}.aqsp-row-header .aqsp-unhide-marker{cursor:row-resize;opacity:.7;background:repeating-linear-gradient(#2563eb 0 1px,#0000 1px 3px);position:absolute;left:0;right:0}.aqsp-resize-guide{pointer-events:none;background:#2563eb;position:absolute;top:0;left:0}.aqsp-autofill-handle{box-sizing:content-box;pointer-events:auto;cursor:crosshair;background:#2563eb;border:1px solid #fff;width:6px;height:6px;position:absolute;top:0;left:0}.aqsp-autofill-preview{border:1px dashed #16a34a;position:absolute;top:0;left:0}@media (forced-colors:active){.aqsp-band{outline-offset:-2px;background:0 0;outline:2px solid highlight}.aqsp-cell[data-aqsp-note]:after,.aqsp-cell[data-aqsp-comment]:after,.aqsp-cell[data-aqsp-invalid]:before{border:1px solid canvastext;width:6px;height:6px}.aqsp-drag-rect[data-aqsp-drag-mode=point]{background:0 0;border-color:highlight}.aqsp-selection-fill{outline-offset:-2px;background:0 0;border:none;outline:2px solid highlight}.aqsp-clip-marquee:after{background-image:none;border:2px dashed highlight;animation:none}}.aqsp-col-header-cell[data-aqsp-in-selection],.aqsp-row-header-cell[data-aqsp-in-selection]{color:#1d4ed8;background:#dbeafe;font-weight:600}.aqsp-live-region{clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.aqsp-sink{opacity:0;resize:none;pointer-events:auto;cursor:default;border:0;width:1px;height:1px;padding:0;position:absolute}.aqsp-sink[data-aqsp-editing]{opacity:1;z-index:1;box-sizing:border-box;color:#1f2937;caret-color:#1f2937;white-space:pre;outline-offset:-2px;cursor:text;background:#fff;outline:2px solid #2563eb;padding:0 4px;font-family:inherit;font-size:13px;line-height:1;overflow:hidden}.aqsp-sink[data-aqsp-pointing]{caret-color:#16a34a}.aqsp-popover-layer{pointer-events:none;position:absolute;inset:0;overflow:visible}.aqsp-popover-anchor{pointer-events:none;position:absolute;top:0;left:0}.aqsp-popover-anchor[inert]{visibility:hidden}.aqsp-popover{color:#1f2937;text-align:start;white-space:normal;cursor:default;pointer-events:auto;background:#fff;border:1px solid #d1d5db;border-radius:8px;flex-direction:column;width:320px;max-width:320px;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica Neue,Noto Sans JP,sans-serif;font-size:13px;line-height:1.4;display:flex;position:absolute;overflow:hidden;box-shadow:0 1px 2px #00000014,0 8px 24px #00000024}.aqsp-popover:focus-visible{outline-offset:-2px;outline:2px solid #2563eb}.aqsp-popover[data-aqsp-popover-frame=note-tip]{background:#fffbeb;border-color:#f59e0b;box-shadow:0 2px 8px #0000001f}.aqsp-popover-note,.aqsp-popover-comment,.aqsp-popover-link,.aqsp-popover-message{flex-direction:column;min-height:0;padding:10px 12px;display:flex}.aqsp-popover-list{flex-direction:column;min-height:0;padding:6px;display:flex}.aqsp-popover-note-tip{overscroll-behavior:contain;white-space:pre-wrap;overflow-wrap:anywhere;min-height:0;padding:8px 10px;overflow-y:auto}[data-aqsp-popover][data-aqsp-popover-rejected]{border-inline-start:3px solid #dc2626;padding-inline-start:9px}.aqsp-popover-title{color:#374151;align-items:baseline;gap:6px;margin:0 0 6px;font-size:12px;font-weight:600;display:flex}.aqsp-popover-count{color:#6b7280;font-weight:400}.aqsp-popover-textarea,.aqsp-popover-input{width:100%;color:inherit;font:inherit;background:#fff;border:1px solid #d1d5db;border-radius:6px;margin:0;padding:5px 8px;line-height:1.4;display:block}.aqsp-popover-textarea{resize:none;white-space:pre-wrap;flex:auto;height:104px;min-height:56px;overflow:auto}.aqsp-popover-comment .aqsp-popover-textarea{flex:none;height:64px}.aqsp-popover-input{flex:none;height:28px}.aqsp-popover-textarea:focus-visible,.aqsp-popover-input:focus-visible{outline-offset:-1px;border-color:#2563eb;outline:2px solid #2563eb}.aqsp-popover-textarea[aria-invalid=true],.aqsp-popover-input[aria-invalid=true]{border-color:#dc2626}.aqsp-popover-textarea[aria-invalid=true]:focus-visible,.aqsp-popover-input[aria-invalid=true]:focus-visible{border-color:#dc2626;outline-color:#dc2626}.aqsp-popover-hint{color:#6b7280;margin:4px 0 0;font-size:12px}[data-aqsp-popover-rejected] .aqsp-popover-hint{color:#b91c1c}.aqsp-popover-field{flex-direction:column;flex:none;gap:3px;margin:0 0 8px;display:flex}.aqsp-popover-field-label{color:#6b7280;font-size:12px}.aqsp-popover-actions{flex:none;align-items:center;gap:6px;margin-top:8px;display:flex}.aqsp-popover-button{color:#1f2937;min-width:56px;height:26px;font:inherit;cursor:pointer;background:#f9fafb;border:1px solid #d1d5db;border-radius:6px;justify-content:center;align-items:center;margin:0;padding:0 10px;font-size:12px;font-weight:500;line-height:1;display:inline-flex}.aqsp-popover-button:not(:disabled):hover{background:#f3f4f6;border-color:#9ca3af}.aqsp-popover-button:focus-visible{outline-offset:1px;outline:2px solid #2563eb}.aqsp-popover-button:disabled{opacity:.5;cursor:not-allowed}.aqsp-popover-button[data-aqsp-popover-save],.aqsp-popover-button[data-aqsp-popover-post]{color:#fff;background:#2563eb;border-color:#2563eb}.aqsp-popover-button[data-aqsp-popover-save]:not(:disabled):hover,.aqsp-popover-button[data-aqsp-popover-post]:not(:disabled):hover{background:#1d4ed8;border-color:#1d4ed8}.aqsp-popover-button[data-aqsp-popover-close]{color:#6b7280;background:0 0;border-color:#0000;width:26px;min-width:0;margin-inline-start:auto;padding:0;font-size:16px;font-weight:400}.aqsp-popover-button[data-aqsp-popover-close]:not(:disabled):hover{color:#1f2937;background:#f3f4f6;border-color:#0000}.aqsp-popover-thread{overscroll-behavior:contain;flex:auto;min-height:48px;max-height:220px;margin:0 0 8px;padding:0;list-style:none;overflow-y:auto}.aqsp-popover-comment-item{border-top:1px solid #f3f4f6;padding:6px 0}.aqsp-popover-comment-item:first-child{border-top:0;padding-top:0}.aqsp-popover-comment-author{color:#111827;font-size:12px;font-weight:600}.aqsp-popover-comment-time{color:#6b7280;font-variant-numeric:tabular-nums;margin-inline-start:6px;font-size:11px}.aqsp-popover-comment-text{white-space:pre-wrap;overflow-wrap:anywhere;margin:2px 0 0}.aqsp-popover-list .aqsp-popover-input{margin-bottom:4px}.aqsp-popover-options{overscroll-behavior:contain;flex:auto;min-height:0;max-height:240px;overflow-y:auto}.aqsp-popover-option{white-space:nowrap;text-overflow:ellipsis;cursor:pointer;border-radius:4px;padding:5px 8px;overflow:hidden}.aqsp-popover-option:hover{background:#f3f4f6}.aqsp-popover-option[aria-selected=true]{color:#1e3a8a;background:#dbeafe}.aqsp-popover-option:focus-visible{outline-offset:-2px;outline:2px solid #2563eb}.aqsp-popover-empty{color:#6b7280;padding:6px 8px;font-size:12px}.aqsp-popover-message{padding-inline-end:40px;position:relative}.aqsp-popover-message .aqsp-popover-title{margin-bottom:2px}.aqsp-popover-message-text{white-space:pre-wrap;overflow-wrap:anywhere;margin:0}.aqsp-popover-message .aqsp-popover-button[data-aqsp-popover-close]{position:absolute;inset-block-start:6px;inset-inline-end:6px}.aqsp-popover-message[data-aqsp-popover-rejected]{background:#fef2f2}.aqsp-popover-message[data-aqsp-popover-rejected] .aqsp-popover-title{color:#b91c1c}@media (forced-colors:active){.aqsp-popover-option:hover,.aqsp-popover-option[aria-selected=true]{outline-offset:-2px;outline:2px solid highlight}.aqsp-popover-textarea[aria-invalid=true],.aqsp-popover-input[aria-invalid=true]{border-style:dashed}.aqsp-popover-button[data-aqsp-popover-close]{border-color:buttontext}}[data-aqsp-empty]{width:100%;height:100%;display:block}.aqsp-freeze-divider{background:var(--aqsp-divider-color,#9ca3af);pointer-events:none;position:absolute}.aqsp-freeze-divider-v{width:var(--aqsp-divider-width,2px)}.aqsp-freeze-divider-h{height:var(--aqsp-divider-width,2px)}@media (forced-colors:active){.aqsp-freeze-divider{background:canvastext}}}@layer utilities;
|
|
3
|
+
@layer theme,base;@layer components{@layer components{.aqvs-scroll-pane{display:flex;position:relative}.aqvs-scroll-pane[data-self-measured=true]{height:100%}.aqvs-scroll-pane-content{box-sizing:border-box;flex:1;height:100%;position:relative;overflow:hidden}.aqvs-tap-scroll-circle{touch-action:none;-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;transition-property:transform;transition-duration:.1s;transition-timing-function:cubic-bezier(0,0,.2,1);display:flex;position:relative}.aqvs-tap-scroll-circle-visual-outer{border-radius:9999px;position:absolute;inset:0}.aqvs-tap-scroll-circle-gradient{background:linear-gradient(to bottom right,#1d4ed899,#60a5fa8c,#bfdbfe66);border:1px solid #fff6;border-radius:9999px;position:absolute;inset:0;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.aqvs-tap-scroll-circle-inner{border-radius:9999px;position:absolute;inset:18%}.aqvs-sample-visual-pupil{box-sizing:border-box;background-color:#ffffffd9;border:1px solid #ffffff80;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-sample-visual-highlight{background-color:#fffc;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-sample-visual-rod{background-color:#ffffff80;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-scrollbar{z-index:50;cursor:default;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;touch-action:none;background-color:#fff;position:relative}.aqvs-scrollbar-horizontal{flex-direction:row;align-items:stretch;display:flex}.aqvs-scrollbar-vertical{flex-direction:column;align-items:stretch;display:flex}.aqvs-scrollbar[data-visible=false],.aqvs-scrollbar-thumb-wrapper[data-visible=false]{opacity:0;pointer-events:none}.aqvs-scrollbar-tap-circle-wrapper{pointer-events:auto;transition-property:opacity;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);position:absolute}.aqvs-scrollbar-arrow-button{color:#313131;letter-spacing:normal;background-color:#e0e0e0;border:0 solid;justify-content:center;align-items:center;padding:0;font-family:system-ui,sans-serif;font-size:.75rem;font-weight:400;line-height:1rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:flex}.aqvs-scrollbar-arrow-button:focus{outline-offset:2px;outline:2px solid #0000}.aqvs-scrollbar-arrow-button:focus-visible{outline-offset:1px;outline:2px solid #60a5fa}.aqvs-scrollbar-arrow-button:disabled{cursor:not-allowed;opacity:.5}.aqvs-scrollbar-arrow-button:enabled:hover{background-color:#d4d4d4}.aqvs-scrollbar-track{background-color:#f5f5f5;flex:1;position:relative}.aqvs-scrollbar-overlay{pointer-events:none;position:absolute;inset:0}.aqvs-scrollbar-thumb-wrapper{touch-action:none;position:absolute}.aqvs-scrollbar-thumb{transform-origin:50%;background-color:#7f7f7f;transition:transform 80ms ease-out;position:absolute}.aqvs-scrollbar-thumb[data-thumb-state=disabled]{transition:none;transform:none}.aqvs-scrollbar-thumb[data-thumb-state=hover]{background-color:#5f5f5f}.aqvs-scrollbar-thumb[data-thumb-state=dragging]{background-color:#4f4f4f;transition:transform 60ms ease-out}.aqvs-scrollbar-thumb-horizontal{inset:1.5px 0}.aqvs-scrollbar-thumb-horizontal[data-thumb-state=hover]{top:-.5px;bottom:-.5px;transform:scaleY(1.06)}.aqvs-scrollbar-thumb-horizontal[data-thumb-state=dragging]{top:-.5px;bottom:-.5px;transform:scaleY(1.12)}.aqvs-scrollbar-thumb-vertical{inset:0 1.5px}.aqvs-scrollbar-thumb-vertical[data-thumb-state=hover]{left:-.5px;right:-.5px;transform:scaleX(1.06)}.aqvs-scrollbar-thumb-vertical[data-thumb-state=dragging]{left:-.5px;right:-.5px;transform:scaleX(1.12)}.aqvs-scroll-to-edge-button{pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;color:#fff;text-transform:uppercase;letter-spacing:.05em;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background-color:#1f2937cc;border:0 solid;border-radius:9999px;padding:.25rem 3rem;font-family:inherit;font-size:10px;font-weight:500;line-height:1.5;transition-property:transform,background-color;transition-duration:.15s;transform:none;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.aqvs-scroll-to-edge-button:focus{outline-offset:2px;outline:2px solid #0000}.aqvs-scroll-to-edge-button:focus-visible{outline-offset:1px;outline:2px solid #60a5fa}.aqvs-scroll-to-edge-button:hover{background-color:#374151}.aqvs-scroll-to-edge-button:active{transform:scale(.95)}.aqvs-scroll-to-edge-overlay{pointer-events:none;z-index:10;transition-property:opacity;transition-duration:.5s;position:absolute;inset:0}.aqvs-scroll-to-edge-overlay[data-visible=true]{opacity:1}.aqvs-scroll-to-edge-overlay[data-visible=false]{opacity:0}.aqvs-scroll-to-edge-overlay[data-visible=false] .aqvs-scroll-to-edge-button{pointer-events:none}.aqvs-scroll-to-edge-button-container{justify-content:center;display:flex;position:absolute;left:0;right:0}.aqvs-scroll-to-edge-button-container-top{top:.5rem}.aqvs-scroll-to-edge-button-container-bottom{bottom:.5rem}.aqvs-no-items-container{width:100%;position:absolute;top:0}.aqvs-no-items-text{text-align:center;color:#6b7280}.aqvs-item-container,.aqvs-bottom-inset,.aqvs-items-wrapper{width:100%;position:absolute}.aqvs-live-region{clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.aqvs-grid{grid-template-rows:minmax(0,1fr) auto;width:100%;height:100%;display:grid;position:relative;overflow:hidden}.aqvs-grid-has-frozen-rows{grid-template-rows:auto minmax(0,1fr) auto}.aqvs-grid-has-trailing-rows{grid-template-rows:minmax(0,1fr) auto auto}.aqvs-grid-has-frozen-rows.aqvs-grid-has-trailing-rows{grid-template-rows:auto minmax(0,1fr) auto auto}.aqvs-grid-frozen-rows{min-width:0;position:relative;overflow:hidden}.aqvs-grid-frozen-band-row{position:absolute;left:0;overflow:hidden}.aqvs-grid-trailing-rows{min-width:0;position:relative;overflow:hidden}.aqvs-grid-trailing-rows-inner{position:absolute;bottom:0;left:0;right:0}.aqvs-grid-trailing-band-row{position:absolute;left:0;overflow:hidden}.aqvs-grid-main{min-width:0;min-height:0;position:relative}.aqvs-grid-row{width:100%;height:100%;transform:translateX(var(--aqvs-grid-hx-residual,0px));position:relative;overflow:visible}.aqvs-grid-cell{height:100%;position:absolute;top:0;overflow:hidden}.aqvs-grid-row.aqvs-grid-row-frozen-host{transform:none}.aqvs-grid-row-scroll{height:100%;top:0;left:var(--aqvs-grid-frozen-width,0px);right:var(--aqvs-grid-trailing-width,0px);position:absolute;overflow:hidden}.aqvs-grid-row-scroll-inner{width:100%;height:100%;transform:translateX(calc(var(--aqvs-grid-hx-residual,0px) - var(--aqvs-grid-frozen-width,0px)));position:absolute;top:0;left:0}.aqvs-grid-row-trailing{height:100%;top:0;left:max(var(--aqvs-grid-frozen-width,0px), calc(100% - var(--aqvs-grid-trailing-width,0px)));position:absolute;right:0;overflow:hidden}.aqvs-grid-row-trailing-inner{height:100%;width:var(--aqvs-grid-trailing-width,0px);position:absolute;top:0;right:0}.aqvs-grid-hbar-strip{min-width:0;padding-left:var(--aqvs-grid-frozen-width,0px);position:relative}.aqvs-grid-live-region{clip:rect(0 0 0 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.aqvs-grid-tap-circle-wrapper{z-index:50;pointer-events:auto;position:absolute}.aqvs-grid-tap-circle{transition-property:opacity;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}@media (forced-colors:active){.aqvs-tap-scroll-circle{outline-offset:-1px;border-radius:50%;outline:1px solid canvastext}}}.aqsp-root,.aqsp-root *,.aqsp-root :before,.aqsp-root :after{box-sizing:border-box}.aqsp-root{--aqsp-row-header-width:64px;--aqsp-col-header-height:24px;--aqsp-band-row:#0084ff;--aqsp-band-column:#42ff4f;--aqsp-band-intersection:#e1ff00;--aqsp-band-opacity:.2;color:#1f2937;background:#fff;width:100%;height:100%;font-size:13px;line-height:1;position:relative;overflow:hidden}.aqsp-zoom{grid-template-columns:var(--aqsp-row-header-width) minmax(0, 1fr);grid-template-rows:var(--aqsp-col-header-height) minmax(0, 1fr);transform-origin:0 0;width:100%;height:100%;display:grid}.aqsp-corner{background:#f3f4f6;border-bottom:1px solid #d1d5db;border-right:1px solid #d1d5db;grid-area:1/1}.aqsp-col-header{-webkit-user-select:none;user-select:none;background:#f3f4f6;border-bottom:1px solid #d1d5db;grid-area:1/2;position:relative;overflow:hidden}.aqsp-col-header-scroll{height:100%;position:absolute;top:0;right:0;overflow:hidden}.aqsp-row-header-scroll{width:100%;position:absolute;bottom:0;left:0;overflow:hidden}.aqsp-col-header-track{will-change:transform;position:absolute;inset:0}.aqsp-col-header-cell{white-space:nowrap;color:#4b5563;border-right:1px solid #e5e7eb;justify-content:center;align-items:center;height:100%;display:flex;position:absolute;top:0;overflow:hidden}.aqsp-row-header{-webkit-user-select:none;user-select:none;background:#f3f4f6;border-right:1px solid #d1d5db;grid-area:2/1;position:relative;overflow:hidden}.aqsp-row-header-track{will-change:transform;position:absolute;top:0;left:0;right:0}.aqsp-row-header-cell{white-space:nowrap;color:#4b5563;font-variant-numeric:tabular-nums;border-bottom:1px solid #e5e7eb;justify-content:flex-end;align-items:center;padding-right:6px;display:flex;position:absolute;left:0;right:0;overflow:hidden}.aqsp-body{position:absolute;inset:0}.aqsp-row{cursor:default}.aqsp-cell{white-space:nowrap;background:#fff;border-bottom:1px solid #e5e7eb;border-right:1px solid #e5e7eb;align-items:center;padding:0 4px;display:flex}.aqsp-cell[data-aqsp-valign=top]{align-items:flex-start}.aqsp-cell[data-aqsp-valign=bottom]{align-items:flex-end}.aqsp-cell[data-aqsp-wrap]{white-space:pre-wrap;overflow-wrap:anywhere}.aqsp-cell[data-aqsp-note]:after,.aqsp-cell[data-aqsp-comment]:after{content:"";pointer-events:none;border:5px solid #4b5563;border-color:#4b5563 #4b5563 #0000 #0000;width:0;height:0;position:absolute;top:0;right:0}.aqsp-cell[data-aqsp-comment]:after{border-top-color:#f97316;border-right-color:#f97316}.aqsp-cell[data-aqsp-invalid]:before{content:"";pointer-events:none;border:5px solid #0000;border-color:#0000 #0000 #dc2626 #dc2626;width:0;height:0;position:absolute;bottom:0;left:0}.aqsp-cell[data-aqsp-link]{color:#1d4ed8;text-decoration:underline}.aqsp-body-stack{grid-area:2/2;position:relative}.aqsp-overlay{pointer-events:none;position:absolute;inset:0;overflow:hidden}.aqsp-overlay-quad{pointer-events:none;position:absolute;overflow:hidden}.aqsp-selection-fill{mix-blend-mode:multiply;background:#3b82f62e;border:1px solid #2563ebd9;position:absolute;top:0;left:0}.aqsp-active-frame{box-sizing:border-box;border:2px solid #2563eb;position:absolute;top:0;left:0}.aqsp-drag-rect{background:#3b82f61a;border:1px solid #2563eb;position:absolute;top:0;left:0}.aqsp-drag-rect[data-aqsp-drag-mode=subtract]{background:#dc262614;border:1px dashed #dc2626}.aqsp-drag-rect[data-aqsp-drag-mode=point]{background:#16a34a14;border:1px dashed #16a34a}.aqsp-band{opacity:var(--aqsp-band-opacity);pointer-events:none;position:absolute;top:0;left:0}.aqsp-band[data-aqsp-band=row]{background:var(--aqsp-band-row)}.aqsp-band[data-aqsp-band=column]{background:var(--aqsp-band-column)}.aqsp-band[data-aqsp-band=intersection]{background:var(--aqsp-band-intersection)}.aqsp-clip-marquee{position:absolute;top:0;left:0}.aqsp-clip-marquee:after{content:"";top:var(--aqsp-clip-outset-top,-3px);right:var(--aqsp-clip-outset-right,-3px);bottom:var(--aqsp-clip-outset-bottom,-3px);left:var(--aqsp-clip-outset-left,-3px);background-image:linear-gradient(90deg,#2563eb 50%,#fff 50%),linear-gradient(90deg,#2563eb 50%,#fff 50%),linear-gradient(#fff 50%,#2563eb 50%),linear-gradient(#fff 50%,#2563eb 50%);background-position:0 0,8px 100%,0 8px,100% 0;background-repeat:repeat-x,repeat-x,repeat-y,repeat-y;background-size:8px 2px,8px 2px,2px 8px,2px 8px;animation:.4s linear infinite aqsp-marching-ants;position:absolute}@keyframes aqsp-marching-ants{to{background-position:8px 0,0 100%,0 0,100% 8px}}@media (prefers-reduced-motion:reduce){.aqsp-clip-marquee:after{animation:none}}.aqsp-col-resize-strip{cursor:col-resize;height:100%;position:absolute;top:0}.aqsp-row-resize-strip{cursor:row-resize;position:absolute;left:0;right:0}.aqsp-col-header .aqsp-unhide-marker{cursor:col-resize;opacity:.7;background:repeating-linear-gradient(90deg,#2563eb 0 1px,#0000 1px 3px);height:100%;position:absolute;top:0}.aqsp-row-header .aqsp-unhide-marker{cursor:row-resize;opacity:.7;background:repeating-linear-gradient(#2563eb 0 1px,#0000 1px 3px);position:absolute;left:0;right:0}.aqsp-resize-guide{pointer-events:none;background:#2563eb;position:absolute;top:0;left:0}.aqsp-autofill-handle{box-sizing:content-box;pointer-events:auto;cursor:crosshair;background:#2563eb;border:1px solid #fff;width:6px;height:6px;position:absolute;top:0;left:0}.aqsp-autofill-preview{border:1px dashed #16a34a;position:absolute;top:0;left:0}@media (forced-colors:active){.aqsp-band{outline-offset:-2px;background:0 0;outline:2px solid highlight}.aqsp-cell[data-aqsp-note]:after,.aqsp-cell[data-aqsp-comment]:after,.aqsp-cell[data-aqsp-invalid]:before{border:1px solid canvastext;width:6px;height:6px}.aqsp-drag-rect[data-aqsp-drag-mode=point]{background:0 0;border-color:highlight}.aqsp-selection-fill{outline-offset:-2px;background:0 0;border:none;outline:2px solid highlight}.aqsp-clip-marquee:after{background-image:none;border:2px dashed highlight;animation:none}}.aqsp-col-header-cell[data-aqsp-in-selection],.aqsp-row-header-cell[data-aqsp-in-selection]{color:#1d4ed8;background:#dbeafe;font-weight:600}.aqsp-live-region{clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.aqsp-sink{opacity:0;resize:none;pointer-events:auto;cursor:default;border:0;width:1px;height:1px;padding:0;position:absolute}.aqsp-sink[data-aqsp-editing]{opacity:1;z-index:1;box-sizing:border-box;color:#1f2937;caret-color:#1f2937;white-space:pre;outline-offset:-2px;cursor:text;background:#fff;outline:2px solid #2563eb;padding:0 4px;font-family:inherit;font-size:13px;line-height:1;overflow:hidden}.aqsp-sink[data-aqsp-pointing]{caret-color:#16a34a}.aqsp-popover-layer{pointer-events:none;position:absolute;inset:0;overflow:visible}.aqsp-popover-anchor{pointer-events:none;position:absolute;top:0;left:0}.aqsp-popover-anchor[inert]{visibility:hidden}.aqsp-popover{color:#1f2937;text-align:start;white-space:normal;cursor:default;pointer-events:auto;background:#fff;border:1px solid #d1d5db;border-radius:8px;flex-direction:column;width:320px;max-width:320px;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica Neue,Noto Sans JP,sans-serif;font-size:13px;line-height:1.4;display:flex;position:absolute;overflow:hidden;box-shadow:0 1px 2px #00000014,0 8px 24px #00000024}.aqsp-popover:focus-visible{outline-offset:-2px;outline:2px solid #2563eb}.aqsp-popover[data-aqsp-popover-frame=note-tip]{background:#fffbeb;border-color:#f59e0b;box-shadow:0 2px 8px #0000001f}.aqsp-popover-note,.aqsp-popover-comment,.aqsp-popover-link,.aqsp-popover-message{flex-direction:column;min-height:0;padding:10px 12px;display:flex}.aqsp-popover-list{flex-direction:column;min-height:0;padding:6px;display:flex}.aqsp-popover-note-tip{overscroll-behavior:contain;white-space:pre-wrap;overflow-wrap:anywhere;min-height:0;padding:8px 10px;overflow-y:auto}[data-aqsp-popover][data-aqsp-popover-rejected]{border-inline-start:3px solid #dc2626;padding-inline-start:9px}.aqsp-popover-title{color:#374151;align-items:baseline;gap:6px;margin:0 0 6px;font-size:12px;font-weight:600;display:flex}.aqsp-popover-count{color:#6b7280;font-weight:400}.aqsp-popover-textarea,.aqsp-popover-input{width:100%;color:inherit;font:inherit;background:#fff;border:1px solid #d1d5db;border-radius:6px;margin:0;padding:5px 8px;line-height:1.4;display:block}.aqsp-popover-textarea{resize:none;white-space:pre-wrap;flex:auto;height:104px;min-height:56px;overflow:auto}.aqsp-popover-comment .aqsp-popover-textarea{flex:none;height:64px}.aqsp-popover-input{flex:none;height:28px}.aqsp-popover-textarea:focus-visible,.aqsp-popover-input:focus-visible{outline-offset:-1px;border-color:#2563eb;outline:2px solid #2563eb}.aqsp-popover-textarea[aria-invalid=true],.aqsp-popover-input[aria-invalid=true]{border-color:#dc2626}.aqsp-popover-textarea[aria-invalid=true]:focus-visible,.aqsp-popover-input[aria-invalid=true]:focus-visible{border-color:#dc2626;outline-color:#dc2626}.aqsp-popover-hint{color:#6b7280;margin:4px 0 0;font-size:12px}[data-aqsp-popover-rejected] .aqsp-popover-hint{color:#b91c1c}.aqsp-popover-field{flex-direction:column;flex:none;gap:3px;margin:0 0 8px;display:flex}.aqsp-popover-field-label{color:#6b7280;font-size:12px}.aqsp-popover-actions{flex:none;align-items:center;gap:6px;margin-top:8px;display:flex}.aqsp-popover-button{color:#1f2937;min-width:56px;height:26px;font:inherit;cursor:pointer;background:#f9fafb;border:1px solid #d1d5db;border-radius:6px;justify-content:center;align-items:center;margin:0;padding:0 10px;font-size:12px;font-weight:500;line-height:1;display:inline-flex}.aqsp-popover-button:not(:disabled):hover{background:#f3f4f6;border-color:#9ca3af}.aqsp-popover-button:focus-visible{outline-offset:1px;outline:2px solid #2563eb}.aqsp-popover-button:disabled{opacity:.5;cursor:not-allowed}.aqsp-popover-button[data-aqsp-popover-save],.aqsp-popover-button[data-aqsp-popover-post]{color:#fff;background:#2563eb;border-color:#2563eb}.aqsp-popover-button[data-aqsp-popover-save]:not(:disabled):hover,.aqsp-popover-button[data-aqsp-popover-post]:not(:disabled):hover{background:#1d4ed8;border-color:#1d4ed8}.aqsp-popover-button[data-aqsp-popover-close]{color:#6b7280;background:0 0;border-color:#0000;width:26px;min-width:0;margin-inline-start:auto;padding:0;font-size:16px;font-weight:400}.aqsp-popover-button[data-aqsp-popover-close]:not(:disabled):hover{color:#1f2937;background:#f3f4f6;border-color:#0000}.aqsp-popover-thread{overscroll-behavior:contain;flex:auto;min-height:48px;max-height:220px;margin:0 0 8px;padding:0;list-style:none;overflow-y:auto}.aqsp-popover-comment-item{border-top:1px solid #f3f4f6;padding:6px 0}.aqsp-popover-comment-item:first-child{border-top:0;padding-top:0}.aqsp-popover-comment-author{color:#111827;font-size:12px;font-weight:600}.aqsp-popover-comment-time{color:#6b7280;font-variant-numeric:tabular-nums;margin-inline-start:6px;font-size:11px}.aqsp-popover-comment-text{white-space:pre-wrap;overflow-wrap:anywhere;margin:2px 0 0}.aqsp-popover-list .aqsp-popover-input{margin-bottom:4px}.aqsp-popover-options{overscroll-behavior:contain;flex:auto;min-height:0;max-height:240px;overflow-y:auto}.aqsp-popover-option{white-space:nowrap;text-overflow:ellipsis;cursor:pointer;border-radius:4px;padding:5px 8px;overflow:hidden}.aqsp-popover-option:hover{background:#f3f4f6}.aqsp-popover-option[aria-selected=true]{color:#1e3a8a;background:#dbeafe}.aqsp-popover-option:focus-visible{outline-offset:-2px;outline:2px solid #2563eb}.aqsp-popover-empty{color:#6b7280;padding:6px 8px;font-size:12px}.aqsp-popover-message{padding-inline-end:40px;position:relative}.aqsp-popover-message .aqsp-popover-title{margin-bottom:2px}.aqsp-popover-message-text{white-space:pre-wrap;overflow-wrap:anywhere;margin:0}.aqsp-popover-message .aqsp-popover-button[data-aqsp-popover-close]{position:absolute;inset-block-start:6px;inset-inline-end:6px}.aqsp-popover-message[data-aqsp-popover-rejected]{background:#fef2f2}.aqsp-popover-message[data-aqsp-popover-rejected] .aqsp-popover-title{color:#b91c1c}@media (forced-colors:active){.aqsp-popover-option:hover,.aqsp-popover-option[aria-selected=true]{outline-offset:-2px;outline:2px solid highlight}.aqsp-popover-textarea[aria-invalid=true],.aqsp-popover-input[aria-invalid=true]{border-style:dashed}.aqsp-popover-button[data-aqsp-popover-close]{border-color:buttontext}}[data-aqsp-empty]{width:100%;height:100%;display:block}.aqsp-freeze-divider{background:var(--aqsp-divider-color,#9ca3af);pointer-events:none;position:absolute}.aqsp-freeze-divider-v{width:var(--aqsp-divider-width,2px)}.aqsp-freeze-divider-h{height:var(--aqsp-divider-width,2px)}@media (forced-colors:active){.aqsp-freeze-divider{background:canvastext}}}@layer utilities;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiquants/spreadsheet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Excel-grade spreadsheet grid for React: multi-cell selection, TSV/HTML clipboard, in-place editing on top of @aiquants/virtualscroll",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css"
|
|
@@ -34,11 +34,12 @@
|
|
|
34
34
|
"LICENSE.md"
|
|
35
35
|
],
|
|
36
36
|
"peerDependencies": {
|
|
37
|
+
"@aiquants/virtualscroll": "^3.6.1",
|
|
37
38
|
"react": "^19.2.7",
|
|
38
|
-
"react-dom": "^19.2.7"
|
|
39
|
-
"@aiquants/virtualscroll": "^3.5.0"
|
|
39
|
+
"react-dom": "^19.2.7"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
+
"@aiquants/virtualscroll": "3.6.1",
|
|
42
43
|
"@playwright/test": "^1.59.1",
|
|
43
44
|
"@tailwindcss/cli": "^4.1.18",
|
|
44
45
|
"@testing-library/jest-dom": "^6.9.1",
|
|
@@ -55,8 +56,7 @@
|
|
|
55
56
|
"typescript": "^5.9.3",
|
|
56
57
|
"vite": "^7.2.7",
|
|
57
58
|
"vite-plugin-dts": "^4.5.4",
|
|
58
|
-
"vitest": "^3.2.4"
|
|
59
|
-
"@aiquants/virtualscroll": "3.5.0"
|
|
59
|
+
"vitest": "^3.2.4"
|
|
60
60
|
},
|
|
61
61
|
"keywords": [
|
|
62
62
|
"react",
|
package/src/Spreadsheet.tsx
CHANGED
|
@@ -39,9 +39,10 @@
|
|
|
39
39
|
* openPopoverRef / hideNoteTipRef / pasteRef / pendingPasteOptionsRef / ポップオーバーアンカー) を所有する。
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
|
-
import type { VirtualGridHandle, VirtualGridRange } from "@aiquants/virtualscroll"
|
|
42
|
+
import type { ScrollBarTapCircleOptions, VirtualGridHandle, VirtualGridRange } from "@aiquants/virtualscroll"
|
|
43
43
|
import type { ProfilerOnRenderCallback } from "react"
|
|
44
44
|
import { forwardRef, type ReactNode, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react"
|
|
45
|
+
import type { ClipChannel } from "./clipboard/clipPayload.ts"
|
|
45
46
|
import type { PasteOptions } from "./clipboard/pasteSpecial.ts"
|
|
46
47
|
import { type ClipInfo, type PasteOutcome, type PasteSheet, useClipboard } from "./clipboard/useClipboard.ts"
|
|
47
48
|
import { type BorderPreset, type CommandRunner, useCommands } from "./commands/useCommands.ts"
|
|
@@ -62,7 +63,7 @@ import { type CellCoord, type CellRange, clampCoord, makeRange, rangeSize, type
|
|
|
62
63
|
import { type CellBorder, type CellFormat, type CellFormatPatch, formatCellValue } from "./model/cellFormat.ts"
|
|
63
64
|
import { type EditPaint, IDLE_EDIT_PAINT, normalizeColor, resolveEditPaint } from "./model/cellPaint.ts"
|
|
64
65
|
import { buildCfIndex, type CfIndex, type CfMemo, createCfMemo, resolvePaintedFormat } from "./model/conditionalFormat.ts"
|
|
65
|
-
import { type CellEdit, type EditContext, type EditRejection, type EditSource, lazyCellText, resolveCellAlign, type SpreadsheetDataAdapter } from "./model/dataAdapter.ts"
|
|
66
|
+
import { type CellEdit, type EditContext, type EditRejection, type EditSource, isReadOnlyCell, lazyCellText, type ReadOnlyRejection, readOnlyDeclared, resolveCellAlign, type SpreadsheetDataAdapter } from "./model/dataAdapter.ts"
|
|
66
67
|
import { type DataValidationRule, type ValidationVerdict, validateCellText } from "./model/dataValidation.ts"
|
|
67
68
|
import type { ReferenceAxis } from "./model/formulaRefs.ts"
|
|
68
69
|
import { colToViewportX, rowToViewportY } from "./model/frozenPartition.ts"
|
|
@@ -204,6 +205,18 @@ export type SpreadsheetProps = {
|
|
|
204
205
|
* frozenTrailingColumns の行双子。エンジン契約・取消シームは同文)。典型は固定合計行。
|
|
205
206
|
*/
|
|
206
207
|
frozenTrailingRows?: number
|
|
208
|
+
/**
|
|
209
|
+
* Tap-scroll circle tuning (v0.10 — the VirtualGrid `scrollBarOptions.tapScrollCircleOptions`
|
|
210
|
+
* engine-validated pass-through; LIVE prop): position and enable/disable of the touch
|
|
211
|
+
* tap-scroll circle. The engine's `resolveTapScrollCircleOptions` is the validation SSOT —
|
|
212
|
+
* undefined is the engine's declared absence signal (engine defaults), never a value
|
|
213
|
+
* fallback in this package.
|
|
214
|
+
* タップサークル調整 (v0.10 — VirtualGrid `scrollBarOptions.tapScrollCircleOptions` の
|
|
215
|
+
* エンジン検証素通し。ライブ prop): タッチのタップスクロールサークルの位置・有効 / 無効の
|
|
216
|
+
* 調整口。検証の SSOT はエンジンの `resolveTapScrollCircleOptions` — undefined はエンジン
|
|
217
|
+
* 宣言の不在信号 (エンジン既定) で、本パッケージでの値フォールバックなし。
|
|
218
|
+
*/
|
|
219
|
+
tapScrollCircleOptions?: ScrollBarTapCircleOptions
|
|
207
220
|
/**
|
|
208
221
|
* Identity-stable contract (same as adapter): an inline arrow here defeats the CellView memo
|
|
209
222
|
* and turns every host re-render into a full visible re-slice (~800 cells at 16k columns).
|
|
@@ -255,6 +268,39 @@ export type SpreadsheetProps = {
|
|
|
255
268
|
* Escape は WCAG 2.1.2 のフォーカス脱出)。
|
|
256
269
|
*/
|
|
257
270
|
onEditRejected?: (error: unknown, rejection: EditRejection) => void
|
|
271
|
+
/**
|
|
272
|
+
* Read-only refusal notice (v0.10): fired once per refused gesture — the adapter was never
|
|
273
|
+
* called, so this is neither an EditRejection nor an operation limit. Never fired when the
|
|
274
|
+
* adapter does not declare `isCellReadOnly`; omit for silence.
|
|
275
|
+
* 読み取り専用拒否通知 (v0.10)。拒否されたジェスチャ毎に 1 回発火 — adapter は呼ばれて
|
|
276
|
+
* いないため EditRejection でも操作上限でもない。`isCellReadOnly` 未宣言では発火せず、
|
|
277
|
+
* 省略時は通知なし。
|
|
278
|
+
*/
|
|
279
|
+
onReadOnlyRejected?: (rejection: ReadOnlyRejection) => void
|
|
280
|
+
/**
|
|
281
|
+
* AT announcement text for a read-only refusal (v0.10), routed through the live region;
|
|
282
|
+
* omit for silence (no built-in strings). Inherits the announcement debounce + rAF collapse,
|
|
283
|
+
* which is what keeps autorepeat / IME refusal storms from spamming the live region.
|
|
284
|
+
* 読み取り専用拒否の AT 読み上げ文言 (v0.10) — live region 経由。省略で無音 (組み込み文言
|
|
285
|
+
* なし)。読み上げのデバウンス + rAF 合流を継承し、autorepeat / IME の拒否連打でも live
|
|
286
|
+
* region は溢れない。
|
|
287
|
+
*/
|
|
288
|
+
formatReadOnlyAnnouncement?: (rejection: ReadOnlyRejection) => string
|
|
289
|
+
/**
|
|
290
|
+
* Paste outcome summary (v0.10 plan §3.1): the menu channel mirrors `paste(options)`'s
|
|
291
|
+
* resolve value 1:1 (read refusals and the write-time re-gate included); the event channel
|
|
292
|
+
* fires once per gesture that reaches the paste pipeline — the drag-freeze consumption and a
|
|
293
|
+
* null `clipboardData` stay deliberately silent, and an empty grid attaches no listener at
|
|
294
|
+
* all. Cause-specific callbacks (`onClipboardUnavailable` / `onOperationRejected` /
|
|
295
|
+
* `onReadOnlyRejected`) fire FIRST — `"rejected"` can be the trailing summary of one of
|
|
296
|
+
* them. Omit = never fired.
|
|
297
|
+
* 貼り付け結果の要約 (v0.10 計画 §3.1)。menu チャネルは `paste(options)` の resolve 値の
|
|
298
|
+
* 1:1 鏡 (読み拒否・書込時再ゲート含む)、event チャネルは貼り付けパイプラインに到達した
|
|
299
|
+
* ゲスチャ毎 1 回 — ドラッグ凍結消費と `clipboardData` null は意図的無言、空グリッドは
|
|
300
|
+
* リスナ自体が無い。原因特定系 callback が先に発火し、`"rejected"` はその後追いの要約に
|
|
301
|
+
* なり得る。省略時は発火なし。
|
|
302
|
+
*/
|
|
303
|
+
onPasteResolved?: (outcome: PasteOutcome, channel: ClipChannel) => void
|
|
258
304
|
/** Change notice for out-of-package writes (undo / collaboration); each increment repaints visible cells (§6.9-2). / パッケージ外経路のデータ変更通知。 */
|
|
259
305
|
dataVersion?: number
|
|
260
306
|
/** Operation cell-count ceiling, default 100,000 (ADR-12). / 操作セル数上限。 */
|
|
@@ -281,12 +327,19 @@ export type SpreadsheetProps = {
|
|
|
281
327
|
* AT announcement text for the active cell; omit for silence (no built-in strings). `text`
|
|
282
328
|
* is the RAW canonical getCellText; `displayText` is what a sighted user sees (the
|
|
283
329
|
* getCellFormat-formatted string — equals `text` when the cell has no format), so AT can
|
|
284
|
-
* hear "87.0%" for a cell whose raw value is "0.87".
|
|
330
|
+
* hear "87.0%" for a cell whose raw value is "0.87". `readOnly` (v0.10) is
|
|
331
|
+
* declaration-gated three-valued: the key is ABSENT when the adapter does not declare
|
|
332
|
+
* `isCellReadOnly` (payload byte-identical to v0.9), `false` = declared editable, `true` =
|
|
333
|
+
* declared read-only — the navigation-time read-only channel (DOM focus stays on the sink,
|
|
334
|
+
* so `aria-readonly` is inert during Arrow navigation).
|
|
285
335
|
* AT 向け読み上げ文言 (省略で無音)。`text` は素の正準 getCellText、`displayText` は目視と同じ
|
|
286
336
|
* 表示文字列 (getCellFormat 整形後 — 書式なしセルでは text と同値)。素値 "0.87" の表示
|
|
287
|
-
* "87.0%"
|
|
337
|
+
* "87.0%" をそのまま読み上げられる。`readOnly` (v0.10) は宣言ゲートの 3 値: `isCellReadOnly`
|
|
338
|
+
* 未宣言はキー自体なし (payload は v0.9 とバイト同一)、`false` = 宣言済み編集可、`true` =
|
|
339
|
+
* 宣言済み読み取り専用 — 矢印航行時の読み取り専用チャネル (フォーカスはシンク常駐のため
|
|
340
|
+
* `aria-readonly` は航行中の AT に届かない)。
|
|
288
341
|
*/
|
|
289
|
-
formatCellAnnouncement?: (ctx: { row: number; col: number; text: string; displayText: string; columnName: string }) => string
|
|
342
|
+
formatCellAnnouncement?: (ctx: { row: number; col: number; text: string; displayText: string; columnName: string; readOnly?: boolean }) => string
|
|
290
343
|
/** AT announcement for the selection extent ("A1 to C3, 3 rows 3 cols"); omit for silence. / AT 向け選択範囲読み上げ。 */
|
|
291
344
|
formatSelectionAnnouncement?: (ctx: { range: CellRange; anchor: CellCoord; active: CellCoord; rangeRowCount: number; rangeColCount: number; rangeCount: number }) => string
|
|
292
345
|
/**
|
|
@@ -444,6 +497,17 @@ const stepZoom = (value: number, direction: 1 | -1): number => (direction > 0 ?
|
|
|
444
497
|
type SpreadsheetTestProps = {
|
|
445
498
|
/** Test-only CellView re-render counter; never passed in production. / テスト専用カウンタ。 */
|
|
446
499
|
__onCellRender?: ProfilerOnRenderCallback
|
|
500
|
+
/**
|
|
501
|
+
* Test-only virtual-keyboard raise spy (v0.10 plan §6.4 / RC-G2t): called synchronously by
|
|
502
|
+
* the single `raiseVirtualKeyboard` funnel, so spy count ≡ raise executions — a React-batched
|
|
503
|
+
* raise-then-lower mutates no DOM (`inputmode` observer-blind) yet still counts. Never
|
|
504
|
+
* passed in production (zero calls when unset — the `__onCellRender` conduct).
|
|
505
|
+
* テスト専用の仮想キーボード招待スパイ (v0.10 計画 §6.4 / RC-G2t)。単一漏斗
|
|
506
|
+
* `raiseVirtualKeyboard` が同期で呼ぶため、計数 ≡ raise 実行 — React バッチの
|
|
507
|
+
* raise-then-lower は DOM 無変異 (`inputmode` の観測は盲目) でも計数には映る。production
|
|
508
|
+
* では渡さない (未指定 = 呼出しゼロ — `__onCellRender` と同じ品行)。
|
|
509
|
+
*/
|
|
510
|
+
__onVirtualKeyboardRaise?: () => void
|
|
447
511
|
}
|
|
448
512
|
|
|
449
513
|
const DEFAULT_ROW_HEIGHT = 24
|
|
@@ -554,6 +618,7 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
|
|
|
554
618
|
frozenRows = 0,
|
|
555
619
|
frozenTrailingColumns = 0,
|
|
556
620
|
frozenTrailingRows = 0,
|
|
621
|
+
tapScrollCircleOptions,
|
|
557
622
|
renderCell,
|
|
558
623
|
onSelectionChange,
|
|
559
624
|
onCellContextMenu,
|
|
@@ -562,6 +627,8 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
|
|
|
562
627
|
onCopyMultiRange,
|
|
563
628
|
onOperationRejected,
|
|
564
629
|
onEditRejected,
|
|
630
|
+
onReadOnlyRejected,
|
|
631
|
+
formatReadOnlyAnnouncement,
|
|
565
632
|
dataVersion,
|
|
566
633
|
maxOperationCells = DEFAULT_MAX_OPERATION_CELLS,
|
|
567
634
|
enableFillHandle = true,
|
|
@@ -584,6 +651,7 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
|
|
|
584
651
|
onValidationRejected,
|
|
585
652
|
onPasteSpecialRequest,
|
|
586
653
|
onClipboardUnavailable,
|
|
654
|
+
onPasteResolved,
|
|
587
655
|
now = Date.now,
|
|
588
656
|
zoom,
|
|
589
657
|
defaultZoom,
|
|
@@ -591,6 +659,7 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
|
|
|
591
659
|
testId,
|
|
592
660
|
className,
|
|
593
661
|
__onCellRender,
|
|
662
|
+
__onVirtualKeyboardRaise,
|
|
594
663
|
}: SpreadsheetProps & SpreadsheetTestProps,
|
|
595
664
|
ref,
|
|
596
665
|
): ReactNode {
|
|
@@ -1098,6 +1167,28 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
|
|
|
1098
1167
|
[],
|
|
1099
1168
|
)
|
|
1100
1169
|
|
|
1170
|
+
/**
|
|
1171
|
+
* The single read-only refusal funnel (v0.10 plan §6.4 — the SSOT of RC-ANN / RC-B): every
|
|
1172
|
+
* gate (G1..G16) delivers its rejection here, which relays the callback AND the announcement
|
|
1173
|
+
* (`formatReadOnlyAnnouncement` → `announce()` — the debounce is inherited). Both props
|
|
1174
|
+
* omitted = silence = byte-identical; the read-only refusal never rides `onEditRejected` /
|
|
1175
|
+
* `onOperationRejected` (channel purity).
|
|
1176
|
+
* 読み取り専用拒否の単一漏斗 (v0.10 計画 §6.4 — RC-ANN / RC-B の SSOT)。全ゲート (G1..G16)
|
|
1177
|
+
* の拒否はここへ集まり、callback と読み上げ (`formatReadOnlyAnnouncement` → `announce()` —
|
|
1178
|
+
* デバウンス継承) の両方を運ぶ。両 prop 省略 = 無音 = バイト恒等。読み取り専用拒否を
|
|
1179
|
+
* `onEditRejected` / `onOperationRejected` へ流用しない (チャネル純度)。
|
|
1180
|
+
*/
|
|
1181
|
+
const rejectReadOnly = useCallback(
|
|
1182
|
+
(rejection: ReadOnlyRejection) => {
|
|
1183
|
+
onReadOnlyRejected?.(rejection)
|
|
1184
|
+
// 読み上げは prop 宣言時だけ (省略 = 無音 — 組み込み文言なしの家訓)
|
|
1185
|
+
if (formatReadOnlyAnnouncement !== undefined) {
|
|
1186
|
+
announce(formatReadOnlyAnnouncement(rejection))
|
|
1187
|
+
}
|
|
1188
|
+
},
|
|
1189
|
+
[onReadOnlyRejected, formatReadOnlyAnnouncement, announce],
|
|
1190
|
+
)
|
|
1191
|
+
|
|
1101
1192
|
/** The 3-tier announcement rule (§6.9-9, evaluated top-down). / 3 段機械判定 (上から順)。 */
|
|
1102
1193
|
const evaluateAnnouncement = useCallback(
|
|
1103
1194
|
(prev: SelectionState, next: SelectionState) => {
|
|
@@ -1115,7 +1206,18 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
|
|
|
1115
1206
|
const raw = adapter.getCellText(a.row, a.col)
|
|
1116
1207
|
// displayText は目視と同一の整形後文字列 (塗り書式 = base ⊕ CF — getCellFormat 未宣言は raw と同値)
|
|
1117
1208
|
const painted = adapter.getCellFormat === undefined ? undefined : paintedFormatOf(a.row, a.col, () => raw)
|
|
1118
|
-
|
|
1209
|
+
// S2 (v0.10 §6.4): readOnly キーは宣言ゲート — isCellReadOnly 未宣言はキー自体を出さず
|
|
1210
|
+
// payload は v0.9 とバイト同一、宣言時は正負両真理のライブ回答 (RC-S2)
|
|
1211
|
+
announce(
|
|
1212
|
+
formatCellAnnouncement({
|
|
1213
|
+
row: a.row,
|
|
1214
|
+
col: a.col,
|
|
1215
|
+
text: raw,
|
|
1216
|
+
displayText: formatCellValue(raw, painted === undefined ? undefined : painted.value),
|
|
1217
|
+
columnName: columnIndexToName(a.col),
|
|
1218
|
+
...(readOnlyDeclared(adapter) ? { readOnly: isReadOnlyCell(adapter, a.row, a.col) } : {}),
|
|
1219
|
+
}),
|
|
1220
|
+
)
|
|
1119
1221
|
}
|
|
1120
1222
|
}
|
|
1121
1223
|
// (1) 単一 1×1 レンジ → セル読み上げ (素のクリック / 矢印移動)
|
|
@@ -1229,14 +1331,37 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
|
|
|
1229
1331
|
// OnTextInputTypeChanged で進行中の変換を強制終了する: 未編集 → IME 1 文字 → Enter が
|
|
1230
1332
|
// 落ちた利用者報告の根因 — セッション基準の切替は compositionstart の中で属性を変えていた)
|
|
1231
1333
|
const [virtualKeyboard, setVirtualKeyboard] = useState(false)
|
|
1334
|
+
/**
|
|
1335
|
+
* The ONE invitation-raising funnel (v0.10 plan §6.4 / RC-G2t): `setVirtualKeyboard(true)`
|
|
1336
|
+
* has exactly this production site, so the test seam's spy count ≡ raise executions by
|
|
1337
|
+
* construction — a React-batched raise-then-lower mutates no DOM yet still counts. The two
|
|
1338
|
+
* LOWERING sites (`onRootBlur`, the non-mouse root-capture pointerdown) call
|
|
1339
|
+
* `setVirtualKeyboard(false)` directly. The uniqueness invariant is machine-sealed by the
|
|
1340
|
+
* side-(iv) census of `readOnlyCensus.spec.ts` (a second raise site cannot land silently).
|
|
1341
|
+
* 招待 raise の単一漏斗 (v0.10 計画 §6.4 / RC-G2t)。`setVirtualKeyboard(true)` の production
|
|
1342
|
+
* 出現はここだけ — テストシームのスパイ計数 ≡ raise 実行が構成的に成立し、React バッチの
|
|
1343
|
+
* raise-then-lower も DOM 無変異のまま計数に映る。下ろし 2 サイト (`onRootBlur` / 非 mouse の
|
|
1344
|
+
* root capture pointerdown) は `setVirtualKeyboard(false)` を直呼びのまま。単一性の不変条件は
|
|
1345
|
+
* `readOnlyCensus.spec.ts` の側 (iv) census が機械封印 (第 2 raise サイトは無音で着地できない)。
|
|
1346
|
+
*/
|
|
1347
|
+
const raiseVirtualKeyboard = useCallback(() => {
|
|
1348
|
+
__onVirtualKeyboardRaise?.()
|
|
1349
|
+
setVirtualKeyboard(true)
|
|
1350
|
+
}, [__onVirtualKeyboardRaise])
|
|
1232
1351
|
const onRequestEdit = useCallback(
|
|
1233
|
-
(
|
|
1234
|
-
//
|
|
1235
|
-
//
|
|
1236
|
-
|
|
1352
|
+
(cell: CellCoord, caretIndex?: number) => {
|
|
1353
|
+
// G2 (v0.10 §6.4 / 設計 ADR-29 v3.1 — 招待前置が規範): raise は startEdit より先に走る
|
|
1354
|
+
// ため、読み取り専用セルでは招待自体をスキップする。G1 だけでは OSK が拒否より先に
|
|
1355
|
+
// 上がってしまう (キーボード無し端末のフラッシュ)。skip でも startEdit は呼ぶ —
|
|
1356
|
+
// 拒否発火サイトは G1 の 1 箇所 (SSOT)。能力未宣言は従来どおり無条件 raise (バイト恒等)
|
|
1357
|
+
if (!isReadOnlyCell(adapter, cell.row, cell.col)) {
|
|
1358
|
+
// ポインタで開くエディタだけが仮想キーボードを招待する (キーボード無し端末で開ける唯一の経路)。
|
|
1359
|
+
// 変換は進行中でない (dblclick 押下面が先に requestCommit 済み) — 属性変更は安全
|
|
1360
|
+
raiseVirtualKeyboard()
|
|
1361
|
+
}
|
|
1237
1362
|
startEdit("edit", caretIndex)
|
|
1238
1363
|
},
|
|
1239
|
-
[startEdit],
|
|
1364
|
+
[adapter, raiseVirtualKeyboard, startEdit],
|
|
1240
1365
|
)
|
|
1241
1366
|
const getClipRef = useRef<(() => ClipInfo | null) | null>(null)
|
|
1242
1367
|
const getClip = useCallback(() => getClipRef.current?.() ?? null, [])
|
|
@@ -1595,6 +1720,8 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
|
|
|
1595
1720
|
commitEdits,
|
|
1596
1721
|
maxOperationCells,
|
|
1597
1722
|
onOperationRejected,
|
|
1723
|
+
// v0.10: G7 の拒否漏斗 (§6.4)
|
|
1724
|
+
rejectReadOnly,
|
|
1598
1725
|
writeFillPreview: onFillFrame,
|
|
1599
1726
|
repaintOverlay,
|
|
1600
1727
|
xAutoScroller: fillXAutoScroller,
|
|
@@ -1685,6 +1812,8 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
|
|
|
1685
1812
|
rearmSink,
|
|
1686
1813
|
discardClip,
|
|
1687
1814
|
isDragging: isAnyDragging,
|
|
1815
|
+
// v0.10: G1 / G16 の拒否漏斗 (callback + 読み上げの SSOT — §6.4)
|
|
1816
|
+
rejectReadOnly,
|
|
1688
1817
|
// Excel 同様、編集中もセルの水平揃えを保つ (数値セルは右揃えのまま編集)。getCellFormat 未宣言は
|
|
1689
1818
|
// null = v0.1 恒等 (textarea の text-align を触らない)。宣言時は塗り書式 (base ⊕ CF) で判定し
|
|
1690
1819
|
// text は遅延 1 回読み (v0.8 §4.2.13)
|
|
@@ -1746,6 +1875,9 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
|
|
|
1746
1875
|
onClipChange,
|
|
1747
1876
|
onClipboardUnavailable,
|
|
1748
1877
|
onEditRejected,
|
|
1878
|
+
// v0.10: G4 / G5 / G6a / G6b の拒否漏斗 (§6.4) + 貼り付け結果の要約 (§3.1)
|
|
1879
|
+
rejectReadOnly,
|
|
1880
|
+
onPasteResolved,
|
|
1749
1881
|
})
|
|
1750
1882
|
getClipRef.current = clipboard.getClip
|
|
1751
1883
|
discardClipRef.current = clipboard.discardClip
|
|
@@ -1775,6 +1907,8 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
|
|
|
1775
1907
|
onHyperlinkOpen,
|
|
1776
1908
|
anchors: popoverAnchorsRef,
|
|
1777
1909
|
repaintOverlay,
|
|
1910
|
+
// v0.10: G14a / G14b / G14c の拒否漏斗 (§6.4)
|
|
1911
|
+
rejectReadOnly,
|
|
1778
1912
|
})
|
|
1779
1913
|
popoversRef.current = popovers
|
|
1780
1914
|
openPopoverRef.current = popovers.open
|
|
@@ -1834,6 +1968,8 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
|
|
|
1834
1968
|
announce,
|
|
1835
1969
|
formatCommandAnnouncement,
|
|
1836
1970
|
keyboardCellRect,
|
|
1971
|
+
// v0.10: G8–G13 / G15 の拒否漏斗 (§6.4)
|
|
1972
|
+
rejectReadOnly,
|
|
1837
1973
|
})
|
|
1838
1974
|
commandsRef.current = commands
|
|
1839
1975
|
executeCommandRef.current = commands.executeCommand
|
|
@@ -1868,6 +2004,8 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
|
|
|
1868
2004
|
commitEdits,
|
|
1869
2005
|
maxOperationCells,
|
|
1870
2006
|
onOperationRejected,
|
|
2007
|
+
// v0.10: G3 (Delete / Backspace) の拒否漏斗 (§6.4)
|
|
2008
|
+
rejectReadOnly,
|
|
1871
2009
|
getClip,
|
|
1872
2010
|
discardClip,
|
|
1873
2011
|
isDragging: isDraggingAny,
|
|
@@ -2350,7 +2488,8 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
|
|
|
2350
2488
|
}
|
|
2351
2489
|
|
|
2352
2490
|
// live region は読み上げ prop のどれか 1 つでも宣言されたときだけ描く (コマンド通知も同じ領域へ)
|
|
2353
|
-
|
|
2491
|
+
// v0.10: formatReadOnlyAnnouncement も live region の存在条件 (拒否読み上げは announce() 経由 — §6.4)
|
|
2492
|
+
const hasAnnouncement = formatCellAnnouncement !== undefined || formatSelectionAnnouncement !== undefined || formatCommandAnnouncement !== undefined || formatReadOnlyAnnouncement !== undefined
|
|
2354
2493
|
|
|
2355
2494
|
return (
|
|
2356
2495
|
// biome-ignore lint/a11y/useSemanticElements: fieldset はフォーム意味論であり不適。role="group" は Escape 脱出の着地点に現在地名を与えるための選択 (§6.9-7 / §10-4)
|
|
@@ -2437,6 +2576,7 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
|
|
|
2437
2576
|
frozenRows={frozenRows}
|
|
2438
2577
|
frozenTrailingColumns={frozenTrailingColumns}
|
|
2439
2578
|
frozenTrailingRows={frozenTrailingRows}
|
|
2579
|
+
tapScrollCircleOptions={tapScrollCircleOptions}
|
|
2440
2580
|
renderCell={renderCell}
|
|
2441
2581
|
onScroll={onScroll}
|
|
2442
2582
|
onRangeChange={onRangeChange}
|
package/src/clipboard/tiling.ts
CHANGED
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
* the conditional-format translation moves rule ranges by these origins). `tileCells` returns
|
|
11
11
|
* the tile origins it entered (a tile whose origin lies inside the grid, even when the grid edge
|
|
12
12
|
* clips part of it). Grid-edge clipping is silent (the §6.5.3 deviation). estimatePasteCellCount
|
|
13
|
-
* pre-checks WITHOUT materializing (ADR-12).
|
|
13
|
+
* pre-checks WITHOUT materializing (ADR-12). v0.10 (ONE geometry, TWO readers — ADR-28 rule 5):
|
|
14
|
+
* computePasteTargetRect is the tiler's geometry judgment extracted pure (consumed by tileCells
|
|
15
|
+
* AND walked by the G5 / G6a read-only paste walls) and fillExtensionRects is the disjoint
|
|
16
|
+
* ≤2-rect decomposition of box ∖ source the G7 wall walks (bound to expandFill's emission set by
|
|
17
|
+
* the per-geometry set-equality spec, never by restructuring the filler). The private expandCutPaste is the cut-move path:
|
|
14
18
|
* never tiles, and the CALLER rejects grid-edge clipping. Fill (§8.3, barrel-PRIVATE):
|
|
15
19
|
* expandFill fills every box cell OUTSIDE the source with 2D source-start modular anchoring
|
|
16
20
|
* (single-axis bands and diagonal L-shapes share one formula — v0.3), generic over the source
|
|
@@ -28,7 +32,10 @@
|
|
|
28
32
|
* タイルの原点 (タイル毎に 1 つの共有 object — 条件付き書式のレンジ平行移動が使う) を
|
|
29
33
|
* 受け取る。`tileCells` は入ったタイルの原点 (原点がグリッド内のタイル — 端で一部が切れても
|
|
30
34
|
* 残る) を返す。グリッド外は静かに切り捨て (§6.5.3 の逸脱)。estimatePasteCellCount は
|
|
31
|
-
* 展開せず事前見積もる (ADR-12)。
|
|
35
|
+
* 展開せず事前見積もる (ADR-12)。v0.10 (1 幾何 2 読者 — ADR-28 rule 5): computePasteTargetRect
|
|
36
|
+
* はタイラーの幾何判断の純関数抽出 (tileCells が消費し、G5 / G6a の読み取り専用 paste 壁が
|
|
37
|
+
* walk する)、fillExtensionRects は G7 壁が walk する box ∖ source の非交 ≤2 矩形分解
|
|
38
|
+
* (expandFill の発行集合とは幾何毎の集合等値 spec で束ね、filler は再構成しない)。expandCutPaste は cut-move 専用 (タイルしない — グリッド外
|
|
32
39
|
* クリップの拒否は呼び出し側 §6.5.4-4)。fill (§8.3 — barrel 非公開): expandFill はソース
|
|
33
40
|
* 開始端 anchoring の 2D 剰余式で「バウンディングボックス − ソース」へ展開 (単軸帯も斜め
|
|
34
41
|
* L 字も同一式 — v0.3 の斜めフィル) し、ソースセル型に generic で対象毎の emitter を受ける
|
|
@@ -39,7 +46,7 @@
|
|
|
39
46
|
*/
|
|
40
47
|
|
|
41
48
|
import { normalizeAdapterHyperlink } from "../model/cellAnnotations.ts"
|
|
42
|
-
import { type CellCoord, type CellRange, rangeSize } from "../model/cellCoord.ts"
|
|
49
|
+
import { type CellCoord, type CellRange, makeRange, rangeSize } from "../model/cellCoord.ts"
|
|
43
50
|
import { type CellFormat, canonicalizeFormat } from "../model/cellFormat.ts"
|
|
44
51
|
import { type CellEdit, resolveEditText, type SpreadsheetDataAdapter } from "../model/dataAdapter.ts"
|
|
45
52
|
import type { DataValidationRule } from "../model/dataValidation.ts"
|
|
@@ -57,6 +64,45 @@ export const estimatePasteCellCount = (source: readonly (readonly string[])[], t
|
|
|
57
64
|
return isTileTarget ? size.rows * size.cols : srcRows * srcCols
|
|
58
65
|
}
|
|
59
66
|
|
|
67
|
+
/**
|
|
68
|
+
* The grid-clamped rectangle a paste occupies, from dimensions alone (v0.10 — ONE geometry,
|
|
69
|
+
* TWO readers, ADR-28 rule 5): the §6.5.3 table's judgment verbatim — a non-1×1 target whose
|
|
70
|
+
* both extents are exact multiples of the dims tiles (output = target extents), anything else
|
|
71
|
+
* expands once (output = the dims); the origin is `active` for a 1×1 target, else the target's
|
|
72
|
+
* top-left; the output rectangle is clamped to the grid. Null when the origin lies outside the
|
|
73
|
+
* grid or the dims are empty — exactly the case `tileCells` visits nothing. `tileCells` consumes
|
|
74
|
+
* THIS function for its geometry and the G5 / G6a read-only walls walk THIS rect, so the walled
|
|
75
|
+
* rectangle and the written rectangle cannot drift.
|
|
76
|
+
* 寸法だけから貼り付けが占めるグリッド clamp 済み矩形 (v0.10 — 1 幾何 2 読者、ADR-28 rule 5)。
|
|
77
|
+
* §6.5.3 の表の判断そのまま — 非 1×1 かつ両軸が寸法の整数倍のターゲットはタイル (出力 =
|
|
78
|
+
* ターゲット寸法)、それ以外は 1 回展開 (出力 = 寸法)。基点は 1×1 ターゲットなら `active`、
|
|
79
|
+
* さもなくばターゲット左上。出力矩形はグリッドへ clamp。基点がグリッド外か寸法が空なら null —
|
|
80
|
+
* `tileCells` が何も訪問しないケースと厳密一致。`tileCells` はこの関数を幾何として消費し、
|
|
81
|
+
* G5 / G6a の読み取り専用壁もこの矩形を walk する — 壁の矩形と書込矩形は乖離し得ない。
|
|
82
|
+
*/
|
|
83
|
+
export const computePasteTargetRect = (dims: { rows: number; cols: number }, target: CellRange, active: CellCoord, rowCount: number, colCount: number): CellRange | null => {
|
|
84
|
+
// 空寸法は占有矩形なし (tileCells の空ソース早期 return と同じ意味論)
|
|
85
|
+
if (dims.rows === 0 || dims.cols === 0) {
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
const size = rangeSize(target)
|
|
89
|
+
const isSingleTarget = size.rows === 1 && size.cols === 1
|
|
90
|
+
// 整数倍レンジ (1×1 を除く) はタイル、それ以外は 1 回展開 (§6.5.3 の表)
|
|
91
|
+
const isTileTarget = !isSingleTarget && size.rows % dims.rows === 0 && size.cols % dims.cols === 0
|
|
92
|
+
const outRows = isTileTarget ? size.rows : dims.rows
|
|
93
|
+
const outCols = isTileTarget ? size.cols : dims.cols
|
|
94
|
+
// 基点: 単一セル選択なら active、それ以外は現在レンジの左上 (表の規則そのもの)
|
|
95
|
+
const origin = isSingleTarget ? active : { row: target.startRow, col: target.startCol }
|
|
96
|
+
// グリッド外は切り捨て (逸脱 — Excel は拒否。§6.5.3 の表参照): 出力矩形をグリッド境界でクランプ
|
|
97
|
+
const rowEnd = Math.min(outRows, rowCount - origin.row)
|
|
98
|
+
const colEnd = Math.min(outCols, colCount - origin.col)
|
|
99
|
+
// 基点自体がグリッド外 = 訪問セルゼロ (タイラーは何も書かない)
|
|
100
|
+
if (rowEnd <= 0 || colEnd <= 0) {
|
|
101
|
+
return null
|
|
102
|
+
}
|
|
103
|
+
return makeRange(origin, { row: origin.row + rowEnd - 1, col: origin.col + colEnd - 1 })
|
|
104
|
+
}
|
|
105
|
+
|
|
60
106
|
/**
|
|
61
107
|
* Generic tiler — the §6.5.3 table over any cell type (spec 03 §4, v0.8): the one construction
|
|
62
108
|
* point the string paste and the rich paste both delegate to. Visits every output cell in
|
|
@@ -67,7 +113,9 @@ export const estimatePasteCellCount = (source: readonly (readonly string[])[], t
|
|
|
67
113
|
* deviation): the output rectangle is clamped to the grid before visiting, so a tile whose
|
|
68
114
|
* origin is inside the grid is entered (and returned) even when only part of it fits, and a
|
|
69
115
|
* tile entirely outside is never entered. Empty source (no rows or no columns) visits nothing
|
|
70
|
-
* and returns no tiles. Returns the tile origins in the same row-major order.
|
|
116
|
+
* and returns no tiles. Returns the tile origins in the same row-major order. The occupied
|
|
117
|
+
* rectangle itself comes from `computePasteTargetRect` (v0.10 — the shared geometry the G5 wall
|
|
118
|
+
* also walks; ADR-28 rule 5).
|
|
71
119
|
* generic タイラー — §6.5.3 の表を任意セル型で (spec 03 §4、v0.8)。文字列貼り付けとリッチ
|
|
72
120
|
* 貼り付けが委譲する唯一の構築点。出力矩形全体の行優先 (v0.7 のフラットループ順 — タイル毎
|
|
73
121
|
* ではない) で全セルを `(row, col, cell, tile)` で訪問: `cell` はソース要素そのもの (同一参照、
|
|
@@ -75,24 +123,21 @@ export const estimatePasteCellCount = (source: readonly (readonly string[])[], t
|
|
|
75
123
|
* 戻り値にも載せる。グリッド端は静かに切り捨て (copy 経路の逸脱): 出力矩形を訪問前に
|
|
76
124
|
* グリッドへクランプするため、原点がグリッド内のタイルは一部しか収まらなくても入り (戻り値
|
|
77
125
|
* にも載り)、全体が外のタイルには入らない。空ソース (行なし / 列なし) は訪問なし・タイル
|
|
78
|
-
* なし。戻り値は入ったタイルの原点 (同じ行優先順)
|
|
126
|
+
* なし。戻り値は入ったタイルの原点 (同じ行優先順)。占有矩形そのものは `computePasteTargetRect`
|
|
127
|
+
* (v0.10 — G5 壁も walk する共有幾何、ADR-28 rule 5) から得る。
|
|
79
128
|
*/
|
|
80
129
|
export const tileCells = <T>(source: readonly (readonly T[])[], target: CellRange, active: CellCoord, rowCount: number, colCount: number, visit: (row: number, col: number, cell: T, tile: CellCoord) => void): CellCoord[] => {
|
|
81
130
|
const srcRows = source.length
|
|
82
131
|
const srcCols = source[0]?.length ?? 0
|
|
83
|
-
|
|
132
|
+
// 幾何判断 (タイル vs 1 回展開、基点、クランプ) は共有関数 1 箇所 (1 幾何 2 読者 — ADR-28 rule 5)
|
|
133
|
+
const rect = computePasteTargetRect({ rows: srcRows, cols: srcCols }, target, active, rowCount, colCount)
|
|
134
|
+
// null = 空ソースか基点グリッド外 — 訪問なし・タイルなし (従来の早期 return / 空ループと同一挙動)
|
|
135
|
+
if (rect === null) {
|
|
84
136
|
return []
|
|
85
137
|
}
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const outRows = isTileTarget ? size.rows : srcRows
|
|
90
|
-
const outCols = isTileTarget ? size.cols : srcCols
|
|
91
|
-
// 基点: 単一セル選択なら active、それ以外は現在レンジの左上 (表の規則そのもの)
|
|
92
|
-
const origin = isSingleTarget ? active : { row: target.startRow, col: target.startCol }
|
|
93
|
-
// グリッド外は切り捨て (逸脱 — Excel は拒否。§6.5.3 の表参照): 出力矩形をグリッド境界でクランプ
|
|
94
|
-
const rowEnd = Math.min(outRows, rowCount - origin.row)
|
|
95
|
-
const colEnd = Math.min(outCols, colCount - origin.col)
|
|
138
|
+
const origin: CellCoord = { row: rect.startRow, col: rect.startCol }
|
|
139
|
+
const rowEnd = rect.endRow - rect.startRow + 1
|
|
140
|
+
const colEnd = rect.endCol - rect.startCol + 1
|
|
96
141
|
const tiles: CellCoord[] = []
|
|
97
142
|
for (let r0 = 0; r0 < rowEnd; r0 += srcRows) {
|
|
98
143
|
// このタイル行の原点をタイル毎に 1 object だけ作る (同一タイルの全セルへ同じ参照を渡す)
|
|
@@ -212,6 +257,51 @@ export const estimateFillCellCount = (sourceRange: CellRange, box: CellRange): n
|
|
|
212
257
|
return boxSize.rows * boxSize.cols - srcSize.rows * srcSize.cols + rowsRead * colsRead
|
|
213
258
|
}
|
|
214
259
|
|
|
260
|
+
/**
|
|
261
|
+
* The disjoint decomposition of box ∖ source into edge strips (v0.10 — ONE geometry, TWO
|
|
262
|
+
* readers, ADR-28 rules 3/5): one row strip per extended row side (the new rows × the full box
|
|
263
|
+
* width) plus one column strip per extended column side (the new columns × the source's row
|
|
264
|
+
* span). A fill box is the bounding rect of source ∪ ONE hover point, so extension is at most
|
|
265
|
+
* one side per axis and the result is ≤2 disjoint rects (an L on a diagonal drag); the
|
|
266
|
+
* decomposition stays total for any containing box. The G7 read-only wall walks these rects;
|
|
267
|
+
* `expandFill` is NOT restructured to consume them (its row-major emission order is pinned
|
|
268
|
+
* shipped behaviour) — the per-geometry set-equality spec binds the two readers instead. Zero
|
|
269
|
+
* extension (box == source) = empty array; a box not containing the source is a caller bug
|
|
270
|
+
* (RangeError, fail-fast — `expandFill`'s assert family).
|
|
271
|
+
* box ∖ source の非交な縁ストリップ分解 (v0.10 — 1 幾何 2 読者、ADR-28 rules 3/5): 拡張された
|
|
272
|
+
* 行側毎に行ストリップ (新規行 × box 全幅) + 拡張された列側毎に列ストリップ (新規列 × source
|
|
273
|
+
* 行スパン)。fill の box は source ∪ hover 1 点の外接矩形のため拡張は軸毎に片側のみ = 結果は
|
|
274
|
+
* 非交 ≤2 矩形 (斜めドラッグは L 字)。分解自体は包含する任意の box に対して全域。G7 の
|
|
275
|
+
* 読み取り専用壁がこの矩形群を walk する。`expandFill` は再構成しない (行優先の発行順は
|
|
276
|
+
* 出荷ピン) — 両読者は幾何毎の集合等値 spec が束ねる。拡張ゼロ (box == source) = 空配列、
|
|
277
|
+
* source を包含しない box は呼び出し側バグ (RangeError で fail-fast — `expandFill` と同じ
|
|
278
|
+
* assert 系)。
|
|
279
|
+
*/
|
|
280
|
+
export const fillExtensionRects = (sourceRange: CellRange, box: CellRange): readonly CellRange[] => {
|
|
281
|
+
// 包含だけを検査 (assertFillBox と違い box == source は合法 — 拡張ゼロ = 空配列。部分重なりは
|
|
282
|
+
// 呼び出し側バグとして fail-fast)
|
|
283
|
+
const contains = box.startRow <= sourceRange.startRow && box.endRow >= sourceRange.endRow && box.startCol <= sourceRange.startCol && box.endCol >= sourceRange.endCol
|
|
284
|
+
if (!contains) {
|
|
285
|
+
throw new RangeError(`[spreadsheet] fillExtensionRects requires a box containing the source (source ${JSON.stringify(sourceRange)}, box ${JSON.stringify(box)})`)
|
|
286
|
+
}
|
|
287
|
+
const rects: CellRange[] = []
|
|
288
|
+
// 行ストリップ: 新規行 × box 全幅 (source 行スパン外の全セルを被覆)
|
|
289
|
+
if (box.startRow < sourceRange.startRow) {
|
|
290
|
+
rects.push(makeRange({ row: box.startRow, col: box.startCol }, { row: sourceRange.startRow - 1, col: box.endCol }))
|
|
291
|
+
}
|
|
292
|
+
if (box.endRow > sourceRange.endRow) {
|
|
293
|
+
rects.push(makeRange({ row: sourceRange.endRow + 1, col: box.startCol }, { row: box.endRow, col: box.endCol }))
|
|
294
|
+
}
|
|
295
|
+
// 列ストリップ: 新規列 × source 行スパン (行ストリップと非交 — 行は source の行スパンに限る)
|
|
296
|
+
if (box.startCol < sourceRange.startCol) {
|
|
297
|
+
rects.push(makeRange({ row: sourceRange.startRow, col: box.startCol }, { row: sourceRange.endRow, col: sourceRange.startCol - 1 }))
|
|
298
|
+
}
|
|
299
|
+
if (box.endCol > sourceRange.endCol) {
|
|
300
|
+
rects.push(makeRange({ row: sourceRange.startRow, col: sourceRange.endCol + 1 }, { row: sourceRange.endRow, col: box.endCol }))
|
|
301
|
+
}
|
|
302
|
+
return rects
|
|
303
|
+
}
|
|
304
|
+
|
|
215
305
|
/**
|
|
216
306
|
* Expands a fill box into cell edits for every cell of the box OUTSIDE the source (§8.3 —
|
|
217
307
|
* copy fill only), generic over the source cell type (v0.8): `readCell` resolves one source
|