waveform-analysis 0.1.36 → 0.1.37

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.
Files changed (28) hide show
  1. package/README.md +3 -3
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +902 -806
  4. package/dist/style.css +1 -1
  5. package/dist/types/components/annotation/WaveformAnnotationEditor.vue.d.ts +4 -0
  6. package/dist/types/components/annotation/useWaveformChartAnnotations.d.ts +3 -1
  7. package/dist/types/components/core/constants.d.ts +3 -1
  8. package/dist/types/components/core/useWaveformChartController.d.ts +2 -0
  9. package/package.json +1 -1
  10. package/src/App.test.ts +3 -2
  11. package/src/App.vue +1 -1
  12. package/src/components/WaveformChartView.vue +10 -1
  13. package/src/components/annotation/WaveformAnnotationEditor.css +35 -104
  14. package/src/components/annotation/WaveformAnnotationEditor.vue +87 -37
  15. package/src/components/annotation/components.test.ts +134 -20
  16. package/src/components/annotation/markup.test.ts +18 -2
  17. package/src/components/annotation/markup.ts +33 -0
  18. package/src/components/annotation/useWaveformChartAnnotations.ts +53 -12
  19. package/src/components/core/constants.test.ts +29 -0
  20. package/src/components/core/constants.ts +31 -9
  21. package/src/components/waveformChartCases/annotationEditingAndReprojection.test.ts +13 -3
  22. package/src/components/waveformChartCases/dataAndHoverPerformance.test.ts +40 -0
  23. package/src/components/waveformChartCases/interactionAndAnnotationCreation.test.ts +24 -14
  24. package/src/components/waveformChartCases/legendVisibility.test.ts +10 -2
  25. package/src/components/waveformChartCases/multiAxisAndPagination.test.ts +1 -1
  26. package/src/components/waveformChartCases/presentationMode.test.ts +10 -2
  27. package/src/components/waveformChartCases/renderingStyles.test.ts +1 -1
  28. package/src/components/waveformChartCases/xDomainStrategy.test.ts +60 -0
package/dist/style.css CHANGED
@@ -1,2 +1,2 @@
1
- .waveform-annotation-layer[data-v-7254383e]{pointer-events:none}.waveform-annotation-layer--interactive .waveform-annotation[data-v-7254383e]{pointer-events:auto;cursor:grab;touch-action:none}.waveform-annotation-layer--interactive .waveform-annotation[data-v-7254383e]:active{cursor:grabbing}.waveform-annotation__arrow[data-v-7254383e]{stroke-width:2px;stroke-linecap:round;pointer-events:none}.waveform-annotation__box[data-v-7254383e]{stroke-width:1px;rx:3}.waveform-annotation-layer--interactive .waveform-annotation__box[data-v-7254383e]{pointer-events:auto}.waveform-annotation__text[data-v-7254383e]{pointer-events:none;-webkit-user-select:none;user-select:none}.waveform-annotation-context-menu[data-v-c70c98a1]{z-index:30;background:#fff;border:1px solid #dfe5ef;border-radius:4px;min-width:112px;padding:4px;display:grid;position:absolute;box-shadow:0 4px 14px #10182829}.waveform-annotation-context-menu button[data-v-c70c98a1]{color:#344054;text-align:left;white-space:nowrap;cursor:pointer;background:0 0;border:0;border-radius:3px;padding:7px 10px;font-size:12px}.waveform-annotation-context-menu button[data-v-c70c98a1]:hover{background:#f2f4f7}.waveform-annotation-editor[data-v-52818b45]{z-index:40;background:#10182852;place-items:center;padding:20px;animation:.16s ease-out waveform-annotation-editor-fade-in-52818b45;display:grid;position:absolute;inset:0}.waveform-annotation-editor__panel[data-v-52818b45]{color:#344054;background:#fff;border:1px solid #e4e7ec;border-radius:12px;gap:20px;width:min(440px,100%);max-height:100%;padding:22px;font-size:13px;animation:.18s ease-out waveform-annotation-editor-rise-in-52818b45;display:grid;overflow-y:auto;box-shadow:0 20px 50px #10182838}.waveform-annotation-editor__header[data-v-52818b45]{justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.waveform-annotation-editor__header h2[data-v-52818b45]{color:#101828;margin:0;font-size:18px;line-height:1.3}.waveform-annotation-editor__header p[data-v-52818b45]{color:#667085;margin:5px 0 0;font-size:12px}.waveform-annotation-editor__close[data-v-52818b45]{color:#667085;cursor:pointer;background:0 0;border:0;border-radius:6px;flex:0 0 30px;place-items:center;width:30px;height:30px;padding:0;font-size:22px;line-height:1;display:inline-grid}.waveform-annotation-editor__close[data-v-52818b45]:hover{color:#101828;background:#f2f4f7}.waveform-annotation-editor__coordinates[data-v-52818b45]{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;display:grid}.waveform-annotation-editor__coordinates span[data-v-52818b45],.waveform-annotation-editor__series[data-v-52818b45]{background:#f8fafc;border:1px solid #eaecf0;border-radius:7px;align-items:center;gap:8px;min-width:0;padding:9px 10px;display:flex}.waveform-annotation-editor__coordinates span[data-v-52818b45]{color:#667085}.waveform-annotation-editor__coordinates b[data-v-52818b45]{color:#1677ff;font-size:11px}.waveform-annotation-editor__coordinates code[data-v-52818b45]{color:#344054;text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:SFMono-Regular,Consolas,monospace;font-size:11px;overflow:hidden}.waveform-annotation-editor__series[data-v-52818b45]{gap:10px}.waveform-annotation-editor__series i[data-v-52818b45]{border:1px solid #0000001f;border-radius:2px;flex:0 0 12px;width:12px;height:12px}.waveform-annotation-editor__series-label[data-v-52818b45]{gap:1px;min-width:0;display:grid}.waveform-annotation-editor__series select[data-v-52818b45]{color:#344054;min-width:0;max-width:190px;font:inherit;background:#fff;border:1px solid #d0d5dd;border-radius:5px;margin-left:auto;padding:5px 7px}.waveform-annotation-editor__series small[data-v-52818b45]{color:#667085;font-size:10px}.waveform-annotation-editor__series b[data-v-52818b45]{color:#344054;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.waveform-annotation-editor__series code[data-v-52818b45]{color:#667085;margin-left:auto;font-size:11px}.waveform-annotation-editor__field[data-v-52818b45],.waveform-annotation-editor__color-field[data-v-52818b45]{gap:4px;display:grid}.waveform-annotation-editor__label-row[data-v-52818b45]{justify-content:space-between;align-items:center;gap:8px;display:flex}.waveform-annotation-editor__label-row b[data-v-52818b45],.waveform-annotation-editor__colors legend[data-v-52818b45]{color:#344054;font-size:12px;font-weight:600}.waveform-annotation-editor__label-row small[data-v-52818b45]{color:#98a2b3;font-size:11px}.waveform-annotation-editor textarea[data-v-52818b45]{box-sizing:border-box;color:#1d2939;width:100%;min-height:92px;font:inherit;resize:vertical;background:#fff;border:1px solid #d0d5dd;border-radius:7px;padding:10px 11px;line-height:1.5}.waveform-annotation-editor textarea[data-v-52818b45]:focus{border-color:#1677ff;outline:3px solid #1677ff24}.waveform-annotation-editor__colors[data-v-52818b45]{border:0;border-top:1px solid #eaecf0;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;min-width:0;margin:0;padding:14px 0 0;display:grid}.waveform-annotation-editor__colors legend[data-v-52818b45]{grid-column:1/-1;padding:0}.waveform-annotation-editor__color-field[data-v-52818b45]{color:#667085;text-align:left;place-items:stretch stretch;min-width:0;font-size:11px}.waveform-annotation-editor__color-field[data-v-52818b45] .vc-color-wrap{border:1px solid #cfd5df;border-radius:6px;width:100%;height:34px;margin-right:0;box-shadow:inset 0 0 0 1px #ffffff61}.waveform-annotation-editor__color-field[data-v-52818b45] .vc-color-wrap:hover{border-color:#98a2b3;box-shadow:0 0 0 3px #1677ff1f}.waveform-annotation-editor__actions[data-v-52818b45]{border-top:1px solid #eaecf0;justify-content:flex-end;gap:8px;padding-top:8px;display:flex}.waveform-annotation-editor__actions button[data-v-52818b45]{color:#475467;cursor:pointer;background:#fff;border:1px solid #d0d5dd;border-radius:6px;height:34px;padding:0 12px}.waveform-annotation-editor__actions button.is-primary[data-v-52818b45]{color:#fff;background:#1677ff;border-color:#1677ff}.waveform-annotation-editor__actions button[data-v-52818b45]:disabled{opacity:.45;cursor:not-allowed}@keyframes waveform-annotation-editor-fade-in-52818b45{0%{opacity:0}to{opacity:1}}@keyframes waveform-annotation-editor-rise-in-52818b45{0%{opacity:0;transform:translateY(8px)scale(.98)}to{opacity:1;transform:translateY(0)scale(1)}}@media (width<=420px){.waveform-annotation-editor[data-v-52818b45]{padding:12px}.waveform-annotation-editor__panel[data-v-52818b45]{gap:16px;padding:18px}.waveform-annotation-editor__coordinates[data-v-52818b45]{grid-template-columns:1fr}}.waveform-tooltip[data-v-1f55d6c8]{box-sizing:border-box;z-index:2;color:#505050;pointer-events:none;background:#fff;border:1px solid #e3e7eb;border-radius:4px;gap:2px;width:max-content;max-width:min(560px,100% - 16px);padding:9px 12px;font:14px/1.35 Arial,sans-serif;display:grid;position:absolute;box-shadow:0 3px 10px #10182826}.waveform-tooltip__series[data-v-1f55d6c8]{grid-template-columns:8px minmax(0,1fr);align-items:start;gap:6px;display:grid}.waveform-tooltip__series i[data-v-1f55d6c8]{border-radius:50%;flex:none;width:8px;height:8px;margin-top:4px}.waveform-tooltip__series-content[data-v-1f55d6c8]{flex-wrap:nowrap;align-items:baseline;gap:12px;min-width:0;display:flex}.waveform-tooltip__series-label[data-v-1f55d6c8]{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}.waveform-tooltip__value[data-v-1f55d6c8]{white-space:nowrap;flex:none}.waveform-tooltip__series strong[data-v-1f55d6c8]{font-weight:600}.waveform-tooltip__value[data-v-1f55d6c8]{color:#555}.waveform-tooltip__series small[data-v-1f55d6c8]{color:#667085}.waveform-track__line[data-v-42a3fd47]{fill:none;stroke-width:1.5px;stroke-linejoin:round;stroke-linecap:round}.waveform-track__error-bar[data-v-42a3fd47]{fill:none}.waveform-track__point[data-v-42a3fd47],.waveform-track__error-bar[data-v-42a3fd47]{pointer-events:none}.waveform-track{isolation:isolate;pointer-events:none}.waveform-track__y-axis-label-bg{fill:#fff;opacity:.9;pointer-events:none}.waveform-track__y-axis-label{pointer-events:none;font-size:12px;font-weight:500}.waveform-track__grid{pointer-events:none}.waveform-track__grid--major line{stroke:#dfe5ef;stroke-width:1px}.waveform-track__grid--minor line{stroke:#f2f5fa;stroke-width:1px}.waveform-track__plot-frame,.waveform-track__plot-background{pointer-events:none}.waveform-track__watermark{fill:#1677ff1a;pointer-events:none;-webkit-user-select:none;user-select:none;font-family:Consolas,Monaco,Courier New,monospace}.waveform-track__overlay{fill:#0000;cursor:crosshair;pointer-events:all;touch-action:none}.waveform-track__no-visible-series{fill:#8c8c8c;pointer-events:none;font:13px sans-serif}.waveform-track__overlay.is-zoomable{cursor:grab}.waveform-track__overlay.is-zoomable:active{cursor:grabbing}.waveform-track__overlay.is-annotating{cursor:crosshair}.waveform-track__overlay.is-presentation{cursor:default;pointer-events:none}.waveform-track__zero-line{fill:none;pointer-events:none}.waveform-track__axis-endpoint{fill:#667085;pointer-events:none;font-size:11px}.waveform-track__axis-exponent{fill:#666;font-family:sans-serif;font-size:10px}.waveform-track__axis path,.waveform-track__axis line{stroke:#1f2937}.waveform-track__axis text{fill:#667085;font-size:11px}.waveform-legend[data-v-2ae12e71]{pointer-events:none;overflow:hidden}.waveform-legend__viewport[data-v-2ae12e71]{box-sizing:border-box;width:100%;height:100%;padding:8px;display:flex;overflow:hidden}.waveform-legend__viewport--top-left[data-v-2ae12e71]{justify-content:flex-start;align-items:flex-start}.waveform-legend__viewport--top[data-v-2ae12e71]{justify-content:center;align-items:flex-start}.waveform-legend__viewport--top-right[data-v-2ae12e71]{justify-content:flex-end;align-items:flex-start}.waveform-legend__viewport--right[data-v-2ae12e71]{justify-content:flex-end;align-items:center}.waveform-legend__viewport--bottom-right[data-v-2ae12e71]{justify-content:flex-end;align-items:flex-end}.waveform-legend__viewport--bottom[data-v-2ae12e71]{justify-content:center;align-items:flex-end}.waveform-legend__viewport--bottom-left[data-v-2ae12e71]{justify-content:flex-start;align-items:flex-end}.waveform-legend__viewport--left[data-v-2ae12e71]{justify-content:flex-start;align-items:center}.waveform-legend__panel[data-v-2ae12e71]{color:#344054;border:1px solid #d0d5dde6;border-radius:4px;flex:0 auto;gap:5px 12px;max-width:100%;max-height:100%;padding:5px 7px;font:12px/1.35 sans-serif;display:flex;overflow:hidden}.waveform-legend__panel--interactive[data-v-2ae12e71]{pointer-events:auto}.waveform-legend__panel--horizontal[data-v-2ae12e71]{flex-flow:wrap;align-items:center}.waveform-legend__panel--vertical[data-v-2ae12e71]{flex-flow:column;align-items:stretch}.waveform-legend__item[data-v-2ae12e71]{min-width:0;max-width:160px;color:inherit;font:inherit;text-align:left;white-space:nowrap;appearance:none;background:0 0;border:0;align-items:center;gap:6px;padding:0;display:flex}.waveform-legend__item[data-v-2ae12e71]:disabled{opacity:1}.waveform-legend__panel--interactive .waveform-legend__item[data-v-2ae12e71]{cursor:pointer}.waveform-legend__panel--interactive .waveform-legend__item[data-v-2ae12e71]:focus-visible{outline-offset:2px;outline:2px solid #1677ff}.waveform-legend__item.is-hidden[data-v-2ae12e71]{opacity:.45}.waveform-legend__item.is-hidden .waveform-legend__label[data-v-2ae12e71]{text-decoration:line-through}.waveform-legend__swatch[data-v-2ae12e71]{stroke-linecap:round;stroke-linejoin:round;flex:0 0 26px;width:26px;height:16px;overflow:visible}.waveform-legend__label[data-v-2ae12e71]{text-overflow:ellipsis;min-width:0;overflow:hidden}.waveform-track__crosshair[data-v-a6f82099]{pointer-events:none}.waveform-track__crosshair line[data-v-a6f82099]{stroke:#57617b;stroke-width:1px;stroke-dasharray:4 3}.waveform-chart{box-sizing:border-box;color:#475467;background:#fff;border:1px solid #00000014;border-radius:6px;min-width:0;min-height:180px;position:relative;overflow:hidden}.waveform-chart--clean{border-color:#0000}.waveform-chart__pagination{z-index:2;position:absolute;bottom:6px;right:10px}.waveform-chart__title-area{background:#fff;align-items:center;width:100%;min-width:0;padding:0 24px;display:flex;position:relative;overflow:hidden}.waveform-chart__title-measure{visibility:hidden;pointer-events:none;display:block;position:absolute}.waveform-chart__title-visual{flex:none;min-width:0;position:relative;overflow:visible}.waveform-chart__title-text{transform-origin:50%;line-height:1.2;display:block;position:absolute;top:50%;left:50%;overflow:visible}.waveform-chart__pagination .ant-pagination-item,.waveform-chart__pagination .ant-pagination-prev .ant-pagination-item-link,.waveform-chart__pagination .ant-pagination-next .ant-pagination-item-link{background:#fff;border-color:#d9d9d9}.waveform-chart__pagination .ant-pagination-item-active{border-color:#1677ff}.waveform-chart__grid-slot-placeholder{fill:#fafbfc;stroke:#e4e7ec;stroke-dasharray:4 4;pointer-events:none}.waveform-chart__svg{max-width:100%;display:block}.waveform-chart__overlay{fill:#0000;cursor:crosshair;touch-action:none}.waveform-chart__overlay.is-zoomable{cursor:crosshair}.waveform-chart--panning .waveform-chart__overlay.is-zoomable{cursor:grabbing}.waveform-chart__zoom-selection{fill:#1677ff24;stroke:#1677ff;stroke-width:1px;pointer-events:none}.waveform-chart__overlay.is-annotating{cursor:crosshair}.waveform-chart--presentation .waveform-chart__overlay{cursor:default;pointer-events:none}.waveform-chart__label,.waveform-chart__empty{fill:#595959;font-size:12px}
1
+ .waveform-annotation-layer[data-v-7254383e]{pointer-events:none}.waveform-annotation-layer--interactive .waveform-annotation[data-v-7254383e]{pointer-events:auto;cursor:grab;touch-action:none}.waveform-annotation-layer--interactive .waveform-annotation[data-v-7254383e]:active{cursor:grabbing}.waveform-annotation__arrow[data-v-7254383e]{stroke-width:2px;stroke-linecap:round;pointer-events:none}.waveform-annotation__box[data-v-7254383e]{stroke-width:1px;rx:3}.waveform-annotation-layer--interactive .waveform-annotation__box[data-v-7254383e]{pointer-events:auto}.waveform-annotation__text[data-v-7254383e]{pointer-events:none;-webkit-user-select:none;user-select:none}.waveform-annotation-context-menu[data-v-c70c98a1]{z-index:30;background:#fff;border:1px solid #dfe5ef;border-radius:4px;min-width:112px;padding:4px;display:grid;position:absolute;box-shadow:0 4px 14px #10182829}.waveform-annotation-context-menu button[data-v-c70c98a1]{color:#344054;text-align:left;white-space:nowrap;cursor:pointer;background:0 0;border:0;border-radius:3px;padding:7px 10px;font-size:12px}.waveform-annotation-context-menu button[data-v-c70c98a1]:hover{background:#f2f4f7}.waveform-annotation-editor__content[data-v-8ce79dc8]{color:#344054;gap:20px;font-size:13px;display:grid}.waveform-annotation-editor__coordinates[data-v-8ce79dc8]{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;display:grid}.waveform-annotation-editor__coordinates span[data-v-8ce79dc8],.waveform-annotation-editor__series[data-v-8ce79dc8]{background:#f8fafc;border:1px solid #eaecf0;border-radius:7px;align-items:center;gap:8px;min-width:0;padding:9px 10px;display:flex}.waveform-annotation-editor__coordinates span[data-v-8ce79dc8]{color:#667085}.waveform-annotation-editor__coordinate-input[data-v-8ce79dc8]{color:#667085;background:#f8fafc;border:1px solid #eaecf0;border-radius:7px;align-items:center;gap:8px;min-width:0;padding:9px 10px;display:flex}.waveform-annotation-editor__coordinate-input[data-v-8ce79dc8] .ant-input-number{color:#344054;width:auto;min-width:96px;font-family:SFMono-Regular,Consolas,monospace}.waveform-annotation-editor__coordinate-value[data-v-8ce79dc8]{display:none}.waveform-annotation-editor__coordinate-error[data-v-8ce79dc8]{color:#d92d20;grid-column:1/-1;margin:-12px 0 0;font-size:11px;line-height:1.4}.waveform-annotation-editor__coordinate-hint[data-v-8ce79dc8]{color:#98a2b3;margin:-12px 0 0;font-size:11px;line-height:1.4}.waveform-annotation-editor__coordinates b[data-v-8ce79dc8]{color:#1677ff;font-size:11px}.waveform-annotation-editor__coordinates code[data-v-8ce79dc8]{color:#344054;text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:SFMono-Regular,Consolas,monospace;font-size:11px;overflow:hidden}.waveform-annotation-editor__series[data-v-8ce79dc8]{gap:10px}.waveform-annotation-editor__series i[data-v-8ce79dc8]{border:1px solid #0000001f;border-radius:2px;flex:0 0 12px;width:12px;height:12px}.waveform-annotation-editor__series-label[data-v-8ce79dc8]{gap:1px;min-width:0;display:grid}.waveform-annotation-editor__series select[data-v-8ce79dc8]{color:#344054;min-width:0;max-width:190px;font:inherit;background:#fff;border:1px solid #d0d5dd;border-radius:5px;margin-left:auto;padding:5px 7px}.waveform-annotation-editor__series small[data-v-8ce79dc8]{color:#667085;font-size:10px}.waveform-annotation-editor__series b[data-v-8ce79dc8]{color:#344054;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.waveform-annotation-editor__series code[data-v-8ce79dc8]{color:#667085;margin-left:auto;font-size:11px}.waveform-annotation-editor__field[data-v-8ce79dc8],.waveform-annotation-editor__color-field[data-v-8ce79dc8]{gap:4px;display:grid}.waveform-annotation-editor__label-row[data-v-8ce79dc8]{justify-content:space-between;align-items:center;gap:8px;display:flex}.waveform-annotation-editor__label-row b[data-v-8ce79dc8],.waveform-annotation-editor__colors legend[data-v-8ce79dc8]{color:#344054;font-size:12px;font-weight:600}.waveform-annotation-editor__label-row small[data-v-8ce79dc8]{color:#98a2b3;font-size:11px}.waveform-annotation-editor textarea[data-v-8ce79dc8]{box-sizing:border-box;color:#1d2939;width:100%;min-height:92px;font:inherit;resize:vertical;background:#fff;border:1px solid #d0d5dd;border-radius:7px;padding:10px 11px;line-height:1.5}.waveform-annotation-editor textarea[data-v-8ce79dc8]:focus{border-color:#1677ff;outline:3px solid #1677ff24}.waveform-annotation-editor__colors[data-v-8ce79dc8]{border:0;border-top:1px solid #eaecf0;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;min-width:0;margin:0;padding:14px 0 0;display:grid}.waveform-annotation-editor__colors legend[data-v-8ce79dc8]{grid-column:1/-1;padding:0}.waveform-annotation-editor__color-field[data-v-8ce79dc8]{color:#667085;text-align:left;place-items:stretch stretch;min-width:0;font-size:11px}.waveform-annotation-editor__color-field[data-v-8ce79dc8] .vc-color-wrap{border:1px solid #cfd5df;border-radius:6px;width:100%;height:34px;margin-right:0;box-shadow:inset 0 0 0 1px #ffffff61}.waveform-annotation-editor__color-field[data-v-8ce79dc8] .vc-color-wrap:hover{border-color:#98a2b3;box-shadow:0 0 0 3px #1677ff1f}@media (width<=420px){.waveform-annotation-editor__content[data-v-8ce79dc8]{gap:16px}.waveform-annotation-editor__coordinates[data-v-8ce79dc8]{grid-template-columns:1fr}}.waveform-tooltip[data-v-1f55d6c8]{box-sizing:border-box;z-index:2;color:#505050;pointer-events:none;background:#fff;border:1px solid #e3e7eb;border-radius:4px;gap:2px;width:max-content;max-width:min(560px,100% - 16px);padding:9px 12px;font:14px/1.35 Arial,sans-serif;display:grid;position:absolute;box-shadow:0 3px 10px #10182826}.waveform-tooltip__series[data-v-1f55d6c8]{grid-template-columns:8px minmax(0,1fr);align-items:start;gap:6px;display:grid}.waveform-tooltip__series i[data-v-1f55d6c8]{border-radius:50%;flex:none;width:8px;height:8px;margin-top:4px}.waveform-tooltip__series-content[data-v-1f55d6c8]{flex-wrap:nowrap;align-items:baseline;gap:12px;min-width:0;display:flex}.waveform-tooltip__series-label[data-v-1f55d6c8]{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}.waveform-tooltip__value[data-v-1f55d6c8]{white-space:nowrap;flex:none}.waveform-tooltip__series strong[data-v-1f55d6c8]{font-weight:600}.waveform-tooltip__value[data-v-1f55d6c8]{color:#555}.waveform-tooltip__series small[data-v-1f55d6c8]{color:#667085}.waveform-track__line[data-v-42a3fd47]{fill:none;stroke-width:1.5px;stroke-linejoin:round;stroke-linecap:round}.waveform-track__error-bar[data-v-42a3fd47]{fill:none}.waveform-track__point[data-v-42a3fd47],.waveform-track__error-bar[data-v-42a3fd47]{pointer-events:none}.waveform-track{isolation:isolate;pointer-events:none}.waveform-track__y-axis-label-bg{fill:#fff;opacity:.9;pointer-events:none}.waveform-track__y-axis-label{pointer-events:none;font-size:12px;font-weight:500}.waveform-track__grid{pointer-events:none}.waveform-track__grid--major line{stroke:#dfe5ef;stroke-width:1px}.waveform-track__grid--minor line{stroke:#f2f5fa;stroke-width:1px}.waveform-track__plot-frame,.waveform-track__plot-background{pointer-events:none}.waveform-track__watermark{fill:#1677ff1a;pointer-events:none;-webkit-user-select:none;user-select:none;font-family:Consolas,Monaco,Courier New,monospace}.waveform-track__overlay{fill:#0000;cursor:crosshair;pointer-events:all;touch-action:none}.waveform-track__no-visible-series{fill:#8c8c8c;pointer-events:none;font:13px sans-serif}.waveform-track__overlay.is-zoomable{cursor:grab}.waveform-track__overlay.is-zoomable:active{cursor:grabbing}.waveform-track__overlay.is-annotating{cursor:crosshair}.waveform-track__overlay.is-presentation{cursor:default;pointer-events:none}.waveform-track__zero-line{fill:none;pointer-events:none}.waveform-track__axis-endpoint{fill:#667085;pointer-events:none;font-size:11px}.waveform-track__axis-exponent{fill:#666;font-family:sans-serif;font-size:10px}.waveform-track__axis path,.waveform-track__axis line{stroke:#1f2937}.waveform-track__axis text{fill:#667085;font-size:11px}.waveform-legend[data-v-2ae12e71]{pointer-events:none;overflow:hidden}.waveform-legend__viewport[data-v-2ae12e71]{box-sizing:border-box;width:100%;height:100%;padding:8px;display:flex;overflow:hidden}.waveform-legend__viewport--top-left[data-v-2ae12e71]{justify-content:flex-start;align-items:flex-start}.waveform-legend__viewport--top[data-v-2ae12e71]{justify-content:center;align-items:flex-start}.waveform-legend__viewport--top-right[data-v-2ae12e71]{justify-content:flex-end;align-items:flex-start}.waveform-legend__viewport--right[data-v-2ae12e71]{justify-content:flex-end;align-items:center}.waveform-legend__viewport--bottom-right[data-v-2ae12e71]{justify-content:flex-end;align-items:flex-end}.waveform-legend__viewport--bottom[data-v-2ae12e71]{justify-content:center;align-items:flex-end}.waveform-legend__viewport--bottom-left[data-v-2ae12e71]{justify-content:flex-start;align-items:flex-end}.waveform-legend__viewport--left[data-v-2ae12e71]{justify-content:flex-start;align-items:center}.waveform-legend__panel[data-v-2ae12e71]{color:#344054;border:1px solid #d0d5dde6;border-radius:4px;flex:0 auto;gap:5px 12px;max-width:100%;max-height:100%;padding:5px 7px;font:12px/1.35 sans-serif;display:flex;overflow:hidden}.waveform-legend__panel--interactive[data-v-2ae12e71]{pointer-events:auto}.waveform-legend__panel--horizontal[data-v-2ae12e71]{flex-flow:wrap;align-items:center}.waveform-legend__panel--vertical[data-v-2ae12e71]{flex-flow:column;align-items:stretch}.waveform-legend__item[data-v-2ae12e71]{min-width:0;max-width:160px;color:inherit;font:inherit;text-align:left;white-space:nowrap;appearance:none;background:0 0;border:0;align-items:center;gap:6px;padding:0;display:flex}.waveform-legend__item[data-v-2ae12e71]:disabled{opacity:1}.waveform-legend__panel--interactive .waveform-legend__item[data-v-2ae12e71]{cursor:pointer}.waveform-legend__panel--interactive .waveform-legend__item[data-v-2ae12e71]:focus-visible{outline-offset:2px;outline:2px solid #1677ff}.waveform-legend__item.is-hidden[data-v-2ae12e71]{opacity:.45}.waveform-legend__item.is-hidden .waveform-legend__label[data-v-2ae12e71]{text-decoration:line-through}.waveform-legend__swatch[data-v-2ae12e71]{stroke-linecap:round;stroke-linejoin:round;flex:0 0 26px;width:26px;height:16px;overflow:visible}.waveform-legend__label[data-v-2ae12e71]{text-overflow:ellipsis;min-width:0;overflow:hidden}.waveform-track__crosshair[data-v-a6f82099]{pointer-events:none}.waveform-track__crosshair line[data-v-a6f82099]{stroke:#57617b;stroke-width:1px;stroke-dasharray:4 3}.waveform-chart{box-sizing:border-box;color:#475467;background:#fff;border:1px solid #00000014;border-radius:6px;min-width:0;min-height:180px;position:relative;overflow:hidden}.waveform-chart--clean{border-color:#0000}.waveform-chart__pagination{z-index:2;position:absolute;bottom:6px;right:10px}.waveform-chart__title-area{background:#fff;align-items:center;width:100%;min-width:0;padding:0 24px;display:flex;position:relative;overflow:hidden}.waveform-chart__title-measure{visibility:hidden;pointer-events:none;display:block;position:absolute}.waveform-chart__title-visual{flex:none;min-width:0;position:relative;overflow:visible}.waveform-chart__title-text{transform-origin:50%;line-height:1.2;display:block;position:absolute;top:50%;left:50%;overflow:visible}.waveform-chart__pagination .ant-pagination-item,.waveform-chart__pagination .ant-pagination-prev .ant-pagination-item-link,.waveform-chart__pagination .ant-pagination-next .ant-pagination-item-link{background:#fff;border-color:#d9d9d9}.waveform-chart__pagination .ant-pagination-item-active{border-color:#1677ff}.waveform-chart__grid-slot-placeholder{fill:#fafbfc;stroke:#e4e7ec;stroke-dasharray:4 4;pointer-events:none}.waveform-chart__svg{max-width:100%;display:block}.waveform-chart__overlay{fill:#0000;cursor:crosshair;touch-action:none}.waveform-chart__overlay.is-zoomable{cursor:crosshair}.waveform-chart--panning .waveform-chart__overlay.is-zoomable{cursor:grabbing}.waveform-chart__zoom-selection{fill:#1677ff24;stroke:#1677ff;stroke-width:1px;pointer-events:none}.waveform-chart__overlay.is-annotating{cursor:crosshair}.waveform-chart--presentation .waveform-chart__overlay{cursor:default;pointer-events:none}.waveform-chart__label,.waveform-chart__empty{fill:#595959;font-size:12px}
2
2
  /*$vite$:1*/
@@ -8,17 +8,21 @@ interface Props {
8
8
  series?: AnnotationSeriesInfo;
9
9
  seriesOptions?: AnnotationSeriesCandidate[];
10
10
  timeUnit?: TimeUnit;
11
+ timeError?: string;
11
12
  }
12
13
  declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
13
14
  cancel: () => any;
14
15
  confirm: (annotation: WaveformAnnotation) => any;
15
16
  "series-change": (seriesId: string) => any;
17
+ "time-change": (displayValue: string) => any;
16
18
  }, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
17
19
  onCancel?: (() => any) | undefined;
18
20
  onConfirm?: ((annotation: WaveformAnnotation) => any) | undefined;
19
21
  "onSeries-change"?: ((seriesId: string) => any) | undefined;
22
+ "onTime-change"?: ((displayValue: string) => any) | undefined;
20
23
  }>, {
21
24
  timeUnit: TimeUnit;
25
+ timeError: string;
22
26
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
23
27
  declare const _default: typeof __VLS_export;
24
28
  export default _default;
@@ -1,5 +1,5 @@
1
1
  import { type ScaleLinear } from 'd3';
2
- import type { ComputedRef, Ref } from 'vue';
2
+ import { type ComputedRef, type Ref } from 'vue';
3
3
  import type { WaveformAnnotation, WaveformInteractionMode } from '../data/types';
4
4
  import type { DisplaySeries, TrackLayout } from '../core/types';
5
5
  import type { ResolvedWaveformChartProps, WaveformChartEmit } from '../core/waveformChartTypes';
@@ -29,6 +29,8 @@ interface AnnotationContext {
29
29
  export declare function useWaveformChartAnnotations(context: AnnotationContext): {
30
30
  toggleSeriesVisibility: (seriesId: string) => void;
31
31
  changeDraftSeries: (seriesId: string) => void;
32
+ changeDraftTime: (displayValue: string) => void;
33
+ timeError: Ref<string, string>;
32
34
  cancelAnnotation: () => void;
33
35
  resolveTrackAtPointer: (pointerX: number, pointerY: number, trackIndex?: number) => TrackLayout | undefined;
34
36
  handleAnnotationClick: (event: MouseEvent, trackIndex?: number) => void;
@@ -97,7 +97,9 @@ export declare const TITLE_CHAR_WIDTH_RATIO = 0.62;
97
97
  */
98
98
  export declare const TITLE_LINE_HEIGHT = 1.2;
99
99
  /**
100
- * 通道默认颜色列表
100
+ * 多系列波形的默认颜色列表。
101
+ *
102
+ * 颜色保持较深并具备足够辨识度,避免大量系列同时绘制时使用浅色导致线条难以观察。
101
103
  */
102
104
  export declare const channelColors: string[];
103
105
  /**
@@ -231,6 +231,8 @@ export declare function useWaveformChartController(props: ResolvedWaveformChartP
231
231
  requestViewportReset: (event: MouseEvent) => void;
232
232
  toggleSeriesVisibility: (seriesId: string) => void;
233
233
  changeDraftSeries: (seriesId: string) => void;
234
+ changeDraftTime: (displayValue: string) => void;
235
+ timeError: string;
234
236
  cancelAnnotation: () => void;
235
237
  resolveTrackAtPointer: (pointerX: number, pointerY: number, trackIndex?: number) => import("./types").TrackLayout | undefined;
236
238
  handleAnnotationClick: (event: MouseEvent, trackIndex?: number) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waveform-analysis",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "main": "./dist/index.cjs",
5
5
  "module": "./dist/index.js",
6
6
  "types": "./dist/types/index.d.ts",
package/src/App.test.ts CHANGED
@@ -321,8 +321,9 @@ describe('App workspace layout', { timeout: 20_000 }, () => {
321
321
  const styleSelect = frameControls.findAllComponents(Select)[0]
322
322
 
323
323
  expect(colorPickers).toHaveLength(2)
324
- expect(widthInput).toBeDefined()
325
- expect(styleSelect).toBeDefined()
324
+
325
+ const initialFrames = wrapper.findAll('.waveform-chart__plot-frame')
326
+ expect(initialFrames.length > 1 && initialFrames.every((frame) => frame.attributes('stroke-width') === '2')).toBe(true)
326
327
 
327
328
  colorPickers[0].vm.$emit('update:pureColor', 'rgba(220, 38, 38, 0.8)')
328
329
  colorPickers[1].vm.$emit('update:pureColor', 'rgba(14, 165, 233, 0.25)')
package/src/App.vue CHANGED
@@ -32,7 +32,7 @@ const overlayMode = ref<WaveformOverlayMode>('single-axis')
32
32
  const rowCount = ref(4)
33
33
  const columnCount = ref(1)
34
34
  const frameBorderColor = ref('#1f2937')
35
- const frameBorderWidth = ref(1)
35
+ const frameBorderWidth = ref(2)
36
36
  const frameBorderStyle = ref<NonNullable<WaveformFrameStyle['borderStyle']>>('solid')
37
37
  const frameBackgroundColor = ref('rgba(255, 255, 255, 0)')
38
38
  const frameWatermarkVisible = ref(true)
@@ -87,9 +87,11 @@ const {
87
87
  contextMenu,
88
88
  editorSeries,
89
89
  editorSeriesOptions,
90
+ timeError,
90
91
  confirmAnnotation,
91
92
  cancelAnnotation,
92
93
  changeDraftSeries,
94
+ changeDraftTime,
93
95
  editContextAnnotation,
94
96
  deleteContextAnnotation,
95
97
  chartHeight,
@@ -98,6 +100,11 @@ const {
98
100
  setTitleMeasureElement,
99
101
  setSharedOverlayElement,
100
102
  } = toRefs(props.controller)
103
+
104
+ function handleChartPointerLeave() {
105
+ pointerInsideChart.value = false
106
+ handlePointerLeave.value()
107
+ }
101
108
  </script>
102
109
 
103
110
  <template>
@@ -123,7 +130,7 @@ const {
123
130
  :data-plot-margin-bottom="resolvedPlotMargin.bottom"
124
131
  :data-title-area-height="titleAreaHeight"
125
132
  @pointerenter="pointerInsideChart = true"
126
- @pointerleave="pointerInsideChart = false"
133
+ @pointerleave="handleChartPointerLeave"
127
134
  @contextmenu.capture="handleNativeContextMenu"
128
135
  >
129
136
  <div
@@ -332,9 +339,11 @@ const {
332
339
  :series="editorSeries"
333
340
  :series-options="editorSeriesOptions"
334
341
  :time-unit="timeUnit"
342
+ :time-error="timeError"
335
343
  @confirm="confirmAnnotation"
336
344
  @cancel="cancelAnnotation"
337
345
  @series-change="changeDraftSeries"
346
+ @time-change="changeDraftTime"
338
347
  />
339
348
 
340
349
  <WaveformAnnotationContextMenu
@@ -1,63 +1,8 @@
1
- .waveform-annotation-editor {
2
- position: absolute;
3
- inset: 0;
4
- z-index: 40;
5
- display: grid;
6
- place-items: center;
7
- padding: 20px;
8
- background: rgb(16 24 40 / 32%);
9
- animation: waveform-annotation-editor-fade-in 160ms ease-out;
10
- }
11
- .waveform-annotation-editor__panel {
1
+ .waveform-annotation-editor__content {
12
2
  display: grid;
13
3
  gap: 20px;
14
- width: min(440px, 100%);
15
- max-height: 100%;
16
- overflow-y: auto;
17
- padding: 22px;
18
4
  color: #344054;
19
5
  font-size: 13px;
20
- background: #fff;
21
- border: 1px solid #e4e7ec;
22
- border-radius: 12px;
23
- box-shadow: 0 20px 50px rgb(16 24 40 / 22%);
24
- animation: waveform-annotation-editor-rise-in 180ms ease-out;
25
- }
26
- .waveform-annotation-editor__header {
27
- display: flex;
28
- align-items: flex-start;
29
- justify-content: space-between;
30
- gap: 16px;
31
- }
32
- .waveform-annotation-editor__header h2 {
33
- margin: 0;
34
- color: #101828;
35
- font-size: 18px;
36
- line-height: 1.3;
37
- }
38
- .waveform-annotation-editor__header p {
39
- margin: 5px 0 0;
40
- color: #667085;
41
- font-size: 12px;
42
- }
43
- .waveform-annotation-editor__close {
44
- display: inline-grid;
45
- flex: 0 0 30px;
46
- width: 30px;
47
- height: 30px;
48
- padding: 0;
49
- place-items: center;
50
- color: #667085;
51
- font-size: 22px;
52
- line-height: 1;
53
- background: transparent;
54
- border: 0;
55
- border-radius: 6px;
56
- cursor: pointer;
57
- }
58
- .waveform-annotation-editor__close:hover {
59
- color: #101828;
60
- background: #f2f4f7;
61
6
  }
62
7
  .waveform-annotation-editor__coordinates {
63
8
  display: grid;
@@ -78,6 +23,39 @@
78
23
  .waveform-annotation-editor__coordinates span {
79
24
  color: #667085;
80
25
  }
26
+ .waveform-annotation-editor__coordinate-input {
27
+ display: flex;
28
+ min-width: 0;
29
+ align-items: center;
30
+ gap: 8px;
31
+ padding: 9px 10px;
32
+ color: #667085;
33
+ background: #f8fafc;
34
+ border: 1px solid #eaecf0;
35
+ border-radius: 7px;
36
+ }
37
+ .waveform-annotation-editor__coordinate-input :deep(.ant-input-number) {
38
+ width: auto;
39
+ min-width: 96px;
40
+ color: #344054;
41
+ font-family: 'SFMono-Regular', Consolas, monospace;
42
+ }
43
+ .waveform-annotation-editor__coordinate-value {
44
+ display: none;
45
+ }
46
+ .waveform-annotation-editor__coordinate-error {
47
+ grid-column: 1 / -1;
48
+ margin: -12px 0 0;
49
+ color: #d92d20;
50
+ font-size: 11px;
51
+ line-height: 1.4;
52
+ }
53
+ .waveform-annotation-editor__coordinate-hint {
54
+ margin: -12px 0 0;
55
+ color: #98a2b3;
56
+ font-size: 11px;
57
+ line-height: 1.4;
58
+ }
81
59
  .waveform-annotation-editor__coordinates b {
82
60
  color: #1677ff;
83
61
  font-size: 11px;
@@ -205,56 +183,9 @@
205
183
  border-color: #98a2b3;
206
184
  box-shadow: 0 0 0 3px rgb(22 119 255 / 12%);
207
185
  }
208
- .waveform-annotation-editor__actions {
209
- display: flex;
210
- justify-content: flex-end;
211
- gap: 8px;
212
- padding-top: 8px;
213
- border-top: 1px solid #eaecf0;
214
- }
215
- .waveform-annotation-editor__actions button {
216
- height: 34px;
217
- padding: 0 12px;
218
- color: #475467;
219
- background: #fff;
220
- border: 1px solid #d0d5dd;
221
- border-radius: 6px;
222
- cursor: pointer;
223
- }
224
- .waveform-annotation-editor__actions button.is-primary {
225
- color: #fff;
226
- background: #1677ff;
227
- border-color: #1677ff;
228
- }
229
- .waveform-annotation-editor__actions button:disabled {
230
- opacity: 0.45;
231
- cursor: not-allowed;
232
- }
233
- @keyframes waveform-annotation-editor-fade-in {
234
- from {
235
- opacity: 0;
236
- }
237
- to {
238
- opacity: 1;
239
- }
240
- }
241
- @keyframes waveform-annotation-editor-rise-in {
242
- from {
243
- opacity: 0;
244
- transform: translateY(8px) scale(0.98);
245
- }
246
- to {
247
- opacity: 1;
248
- transform: translateY(0) scale(1);
249
- }
250
- }
251
186
  @media (max-width: 420px) {
252
- .waveform-annotation-editor {
253
- padding: 12px;
254
- }
255
- .waveform-annotation-editor__panel {
187
+ .waveform-annotation-editor__content {
256
188
  gap: 16px;
257
- padding: 18px;
258
189
  }
259
190
  .waveform-annotation-editor__coordinates {
260
191
  grid-template-columns: 1fr;
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, nextTick, ref, watch } from 'vue'
3
+ import { InputNumber, Modal } from 'ant-design-vue'
3
4
  import { ColorPicker } from 'vue3-colorpicker'
4
5
  import 'vue3-colorpicker/style.css'
5
6
 
@@ -15,24 +16,44 @@ interface Props {
15
16
  series?: AnnotationSeriesInfo
16
17
  seriesOptions?: AnnotationSeriesCandidate[]
17
18
  timeUnit?: TimeUnit
19
+ timeError?: string
18
20
  }
19
21
 
20
22
  const props = withDefaults(defineProps<Props>(), {
21
23
  timeUnit: 'ms',
24
+ timeError: '',
22
25
  })
23
26
  const emit = defineEmits<{
24
27
  (event: 'confirm', annotation: WaveformAnnotation): void
25
28
  (event: 'cancel'): void
26
29
  (event: 'series-change', seriesId: string): void
30
+ (event: 'time-change', displayValue: string): void
27
31
  }>()
28
32
 
29
33
  const textarea = ref<HTMLTextAreaElement>()
30
34
  const dialogTitleId = useWaveformInstanceId('waveform-annotation-editor-title')
31
35
  const text = ref('')
36
+ const timeInput = ref('')
37
+ const timeValidationRequested = ref(false)
32
38
  const borderColor = ref('')
33
39
  const textColor = ref('')
34
40
  const backgroundColor = ref('')
35
- const canConfirm = computed(() => text.value.trim().length > 0)
41
+ const inputTimeError = computed(() => {
42
+ if (!timeValidationRequested.value) return ''
43
+ if (!timeInput.value.trim() || !Number.isFinite(Number(timeInput.value)))
44
+ return '请输入有效的时间'
45
+ return ''
46
+ })
47
+ const timeErrorMessage = computed(() =>
48
+ timeValidationRequested.value ? inputTimeError.value || props.timeError || '' : '',
49
+ )
50
+ const canConfirm = computed(
51
+ () =>
52
+ text.value.trim().length > 0 &&
53
+ timeInput.value.trim().length > 0 &&
54
+ Number.isFinite(Number(timeInput.value)) &&
55
+ !timeErrorMessage.value,
56
+ )
36
57
  const characterCount = computed(() => text.value.length)
37
58
  const selectedSeries = computed(() => {
38
59
  const option = props.seriesOptions?.find(
@@ -46,15 +67,30 @@ const selectedSeries = computed(() => {
46
67
  function hydrate() {
47
68
  const style = resolveAnnotationStyle(props.annotation.style)
48
69
  text.value = props.annotation.text
70
+ timeInput.value = formatAnnotationTime(props.annotation.x, props.timeUnit)
71
+ timeValidationRequested.value = false
49
72
  borderColor.value = style.borderColor
50
73
  textColor.value = style.textColor
51
74
  backgroundColor.value = style.backgroundColor
52
75
  void nextTick(() => textarea.value?.focus())
53
76
  }
54
77
 
55
- watch(() => props.annotation, hydrate, { immediate: true })
78
+ watch(() => props.annotation.id, hydrate, { immediate: true })
79
+
80
+ function handleTimeInput(value: number | string | null) {
81
+ const nextValue = value === null || value === undefined ? '' : String(value)
82
+ timeInput.value = nextValue
83
+ timeValidationRequested.value = false
84
+ }
85
+
86
+ function commitTimeInput() {
87
+ timeValidationRequested.value = true
88
+ emit('time-change', timeInput.value)
89
+ }
56
90
 
57
- function confirm() {
91
+ async function confirm() {
92
+ commitTimeInput()
93
+ await nextTick()
58
94
  if (!canConfirm.value) return
59
95
  emit('confirm', {
60
96
  ...props.annotation,
@@ -82,40 +118,60 @@ function handleSeriesChange(event: Event) {
82
118
  </script>
83
119
 
84
120
  <template>
85
- <div
86
- class="waveform-annotation-editor"
87
- role="dialog"
88
- aria-modal="true"
89
- :aria-labelledby="dialogTitleId"
90
- @click.self="emit('cancel')"
121
+ <Modal
122
+ :visible="true"
123
+ :width="440"
124
+ :mask-closable="true"
125
+ :keyboard="true"
126
+ cancel-text="取消"
127
+ ok-text="保存标注"
128
+ :ok-button-props="{ disabled: !canConfirm }"
129
+ wrap-class-name="waveform-annotation-editor"
130
+ @cancel="emit('cancel')"
131
+ @ok="confirm"
91
132
  >
92
- <section class="waveform-annotation-editor__panel">
93
- <header class="waveform-annotation-editor__header">
94
- <div>
95
- <h2 :id="dialogTitleId">
96
- {{ props.mode === 'add' ? '添加标注' : '编辑标注' }}
97
- </h2>
98
- </div>
99
- <button
100
- type="button"
101
- class="waveform-annotation-editor__close"
102
- aria-label="关闭标注编辑器"
103
- title="关闭"
104
- @click="emit('cancel')"
105
- >
106
- ×
107
- </button>
108
- </header>
133
+ <template #title>
134
+ <span :id="dialogTitleId">{{ props.mode === 'add' ? '添加标注' : '编辑标注' }}</span>
135
+ </template>
109
136
 
137
+ <div class="waveform-annotation-editor__content">
110
138
  <div class="waveform-annotation-editor__coordinates" aria-label="标注坐标">
111
- <span
112
- ><b>X ({{ props.timeUnit }})</b
113
- ><code>{{ formatAnnotationTime(props.annotation.x, props.timeUnit) }}</code></span
139
+ <label
140
+ class="waveform-annotation-editor__coordinate-input"
141
+ :aria-invalid="Boolean(timeErrorMessage)"
142
+ >
143
+ <b>X</b>
144
+ <InputNumber
145
+ :value="timeInput === '' ? undefined : Number(timeInput)"
146
+ :controls="true"
147
+ :step="0.001"
148
+ :keyboard="true"
149
+ :status="timeErrorMessage ? 'error' : undefined"
150
+ aria-label="标注横轴时间"
151
+ :aria-invalid="Boolean(timeErrorMessage)"
152
+ :aria-describedby="timeErrorMessage ? `${dialogTitleId}-time-error` : undefined"
153
+ @blur="commitTimeInput"
154
+ @update:value="handleTimeInput"
155
+ />
156
+ <code class="waveform-annotation-editor__coordinate-value" aria-hidden="true">{{
157
+ timeInput
158
+ }}</code>
159
+ </label>
160
+ <p
161
+ v-if="timeErrorMessage"
162
+ :id="`${dialogTitleId}-time-error`"
163
+ class="waveform-annotation-editor__coordinate-error"
164
+ role="alert"
114
165
  >
166
+ {{ timeErrorMessage }}
167
+ </p>
115
168
  <span
116
169
  ><b>Y</b><code>{{ formatPlainNumber(props.annotation.y) }}</code></span
117
170
  >
118
171
  </div>
172
+ <p class="waveform-annotation-editor__coordinate-hint" role="note">
173
+ 修改 X 轴后失焦时会自动吸附最近的采样点
174
+ </p>
119
175
 
120
176
  <label
121
177
  v-if="selectedSeries"
@@ -200,14 +256,8 @@ function handleSeriesChange(event: Event) {
200
256
  </label>
201
257
  </fieldset>
202
258
 
203
- <footer class="waveform-annotation-editor__actions">
204
- <button type="button" @click="emit('cancel')">取消</button>
205
- <button type="button" class="is-primary" :disabled="!canConfirm" @click="confirm">
206
- 保存标注
207
- </button>
208
- </footer>
209
- </section>
210
- </div>
259
+ </div>
260
+ </Modal>
211
261
  </template>
212
262
 
213
263
  <style scoped src="./WaveformAnnotationEditor.css"></style>