@dou-ui/sheet 0.0.0-stage → 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/cell-types.js +20 -0
- package/dist/cell-types.js.map +1 -0
- package/dist/code/code-formula-bar.js +6 -0
- package/dist/code/code-formula-bar.js.map +1 -0
- package/dist/code/code-formula-bar.vue_vue_type_script_setup_true_lang.js +192 -0
- package/dist/code/code-formula-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/code/index.js +12 -0
- package/dist/code/index.js.map +1 -0
- package/dist/code/language.js +51 -0
- package/dist/code/language.js.map +1 -0
- package/dist/code/style.d.ts +1 -0
- package/dist/code/style.js +2 -0
- package/dist/collaboration/anchor.js +143 -0
- package/dist/collaboration/anchor.js.map +1 -0
- package/dist/collaboration/index.js +770 -0
- package/dist/collaboration/index.js.map +1 -0
- package/dist/collaboration/peers.js +87 -0
- package/dist/collaboration/peers.js.map +1 -0
- package/dist/collaboration/shared.js +450 -0
- package/dist/collaboration/shared.js.map +1 -0
- package/dist/components/cell-panel.js +6 -0
- package/dist/components/cell-panel.js.map +1 -0
- package/dist/components/cell-panel.vue_vue_type_script_setup_true_lang.js +70 -0
- package/dist/components/cell-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/cell-renders.js +40 -0
- package/dist/components/cell-renders.js.map +1 -0
- package/dist/components/date-panel.js +6 -0
- package/dist/components/date-panel.js.map +1 -0
- package/dist/components/date-panel.vue_vue_type_script_setup_true_lang.js +48 -0
- package/dist/components/date-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/filter-panel.js +6 -0
- package/dist/components/filter-panel.js.map +1 -0
- package/dist/components/filter-panel.vue_vue_type_script_setup_true_lang.js +259 -0
- package/dist/components/filter-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/format-dialog.js +6 -0
- package/dist/components/format-dialog.js.map +1 -0
- package/dist/components/format-dialog.vue_vue_type_script_setup_true_lang.js +432 -0
- package/dist/components/format-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/format-list.js +6 -0
- package/dist/components/format-list.js.map +1 -0
- package/dist/components/format-list.vue_vue_type_script_setup_true_lang.js +187 -0
- package/dist/components/format-list.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/format-presets.js +97 -0
- package/dist/components/format-presets.js.map +1 -0
- package/dist/components/option-panel.js +6 -0
- package/dist/components/option-panel.js.map +1 -0
- package/dist/components/option-panel.vue_vue_type_script_setup_true_lang.js +117 -0
- package/dist/components/option-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-comment.js +6 -0
- package/dist/components/sheet-comment.js.map +1 -0
- package/dist/components/sheet-comment.vue_vue_type_script_setup_true_lang.js +104 -0
- package/dist/components/sheet-comment.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-formula-bar.js +6 -0
- package/dist/components/sheet-formula-bar.js.map +1 -0
- package/dist/components/sheet-formula-bar.vue_vue_type_script_setup_true_lang.js +124 -0
- package/dist/components/sheet-formula-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-link.js +6 -0
- package/dist/components/sheet-link.js.map +1 -0
- package/dist/components/sheet-link.vue_vue_type_script_setup_true_lang.js +249 -0
- package/dist/components/sheet-link.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-menu.js +6 -0
- package/dist/components/sheet-menu.js.map +1 -0
- package/dist/components/sheet-menu.vue_vue_type_script_setup_true_lang.js +115 -0
- package/dist/components/sheet-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-name-box.js +6 -0
- package/dist/components/sheet-name-box.js.map +1 -0
- package/dist/components/sheet-name-box.vue_vue_type_script_setup_true_lang.js +91 -0
- package/dist/components/sheet-name-box.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-note.js +6 -0
- package/dist/components/sheet-note.js.map +1 -0
- package/dist/components/sheet-note.vue_vue_type_script_setup_true_lang.js +146 -0
- package/dist/components/sheet-note.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-outline.js +6 -0
- package/dist/components/sheet-outline.js.map +1 -0
- package/dist/components/sheet-outline.vue_vue_type_script_setup_true_lang.js +237 -0
- package/dist/components/sheet-outline.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-tabs.js +6 -0
- package/dist/components/sheet-tabs.js.map +1 -0
- package/dist/components/sheet-tabs.vue_vue_type_script_setup_true_lang.js +487 -0
- package/dist/components/sheet-tabs.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-toolbar.js +6 -0
- package/dist/components/sheet-toolbar.js.map +1 -0
- package/dist/components/sheet-toolbar.vue_vue_type_script_setup_true_lang.js +1562 -0
- package/dist/components/sheet-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/validation-dialog.js +6 -0
- package/dist/components/validation-dialog.js.map +1 -0
- package/dist/components/validation-dialog.vue_vue_type_script_setup_true_lang.js +370 -0
- package/dist/components/validation-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/context.js +12 -0
- package/dist/context.js.map +1 -0
- package/dist/core/address.js +256 -0
- package/dist/core/address.js.map +1 -0
- package/dist/core/axis.js +72 -0
- package/dist/core/axis.js.map +1 -0
- package/dist/core/cell-type.js +30 -0
- package/dist/core/cell-type.js.map +1 -0
- package/dist/core/clipboard.js +141 -0
- package/dist/core/clipboard.js.map +1 -0
- package/dist/core/commands.js +63 -0
- package/dist/core/commands.js.map +1 -0
- package/dist/core/comments.js +74 -0
- package/dist/core/comments.js.map +1 -0
- package/dist/core/conditional.js +362 -0
- package/dist/core/conditional.js.map +1 -0
- package/dist/core/csv.js +191 -0
- package/dist/core/csv.js.map +1 -0
- package/dist/core/data.js +118 -0
- package/dist/core/data.js.map +1 -0
- package/dist/core/document.js +187 -0
- package/dist/core/document.js.map +1 -0
- package/dist/core/extension.js +60 -0
- package/dist/core/extension.js.map +1 -0
- package/dist/core/file.js +336 -0
- package/dist/core/file.js.map +1 -0
- package/dist/core/fill.js +153 -0
- package/dist/core/fill.js.map +1 -0
- package/dist/core/find.js +42 -0
- package/dist/core/find.js.map +1 -0
- package/dist/core/format.js +404 -0
- package/dist/core/format.js.map +1 -0
- package/dist/core/group.js +134 -0
- package/dist/core/group.js.map +1 -0
- package/dist/core/history.js +68 -0
- package/dist/core/history.js.map +1 -0
- package/dist/core/index.js +17 -0
- package/dist/core/link.js +34 -0
- package/dist/core/link.js.map +1 -0
- package/dist/core/merge.js +105 -0
- package/dist/core/merge.js.map +1 -0
- package/dist/core/model.js +48 -0
- package/dist/core/model.js.map +1 -0
- package/dist/core/nav.js +44 -0
- package/dist/core/nav.js.map +1 -0
- package/dist/core/paint.js +106 -0
- package/dist/core/paint.js.map +1 -0
- package/dist/core/plain-cell-types.js +54 -0
- package/dist/core/plain-cell-types.js.map +1 -0
- package/dist/core/protect.js +246 -0
- package/dist/core/protect.js.map +1 -0
- package/dist/core/selection.js +37 -0
- package/dist/core/selection.js.map +1 -0
- package/dist/core/transaction.js +1582 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/core/validation.js +135 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/core/versions.js +161 -0
- package/dist/core/versions.js.map +1 -0
- package/dist/export/color.js +36 -0
- package/dist/export/color.js.map +1 -0
- package/dist/export/draw.js +316 -0
- package/dist/export/draw.js.map +1 -0
- package/dist/export/index.js +390 -0
- package/dist/export/index.js.map +1 -0
- package/dist/formula/edit.js +210 -0
- package/dist/formula/edit.js.map +1 -0
- package/dist/formula/engine.js +526 -0
- package/dist/formula/engine.js.map +1 -0
- package/dist/formula/error.js +39 -0
- package/dist/formula/error.js.map +1 -0
- package/dist/formula/functions.js +1493 -0
- package/dist/formula/functions.js.map +1 -0
- package/dist/formula/help.js +408 -0
- package/dist/formula/help.js.map +1 -0
- package/dist/formula/index.js +7 -0
- package/dist/formula/parse.js +369 -0
- package/dist/formula/parse.js.map +1 -0
- package/dist/formula/values.js +123 -0
- package/dist/formula/values.js.map +1 -0
- package/dist/formulas.js +30 -0
- package/dist/formulas.js.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/locale.js +1312 -0
- package/dist/locale.js.map +1 -0
- package/dist/sheet.js +6 -0
- package/dist/sheet.js.map +1 -0
- package/dist/sheet.vue_vue_type_script_setup_true_lang.js +7166 -0
- package/dist/sheet.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +17 -0
- package/dist/types/cell-types.d.ts +2 -0
- package/dist/types/code/code-formula-bar.vue.d.ts +3 -0
- package/dist/types/code/index.d.ts +12 -0
- package/dist/types/code/language.d.ts +13 -0
- package/dist/types/collaboration/anchor.d.ts +65 -0
- package/dist/types/collaboration/index.d.ts +59 -0
- package/dist/types/collaboration/peers.d.ts +21 -0
- package/dist/types/collaboration/shared.d.ts +75 -0
- package/dist/types/components/cell-panel.vue.d.ts +20 -0
- package/dist/types/components/cell-renders.d.ts +9 -0
- package/dist/types/components/date-panel.vue.d.ts +9 -0
- package/dist/types/components/filter-panel.vue.d.ts +21 -0
- package/dist/types/components/format-dialog.vue.d.ts +24 -0
- package/dist/types/components/format-list.vue.d.ts +25 -0
- package/dist/types/components/format-presets.d.ts +30 -0
- package/dist/types/components/option-panel.vue.d.ts +13 -0
- package/dist/types/components/sheet-comment.vue.d.ts +29 -0
- package/dist/types/components/sheet-formula-bar.vue.d.ts +3 -0
- package/dist/types/components/sheet-link.vue.d.ts +32 -0
- package/dist/types/components/sheet-menu.vue.d.ts +20 -0
- package/dist/types/components/sheet-name-box.vue.d.ts +3 -0
- package/dist/types/components/sheet-note.vue.d.ts +22 -0
- package/dist/types/components/sheet-outline.vue.d.ts +36 -0
- package/dist/types/components/sheet-tabs.vue.d.ts +9 -0
- package/dist/types/components/sheet-toolbar.vue.d.ts +11 -0
- package/dist/types/components/validation-dialog.vue.d.ts +25 -0
- package/dist/types/context.d.ts +189 -0
- package/dist/types/core/address.d.ts +109 -0
- package/dist/types/core/axis.d.ts +23 -0
- package/dist/types/core/cell-type.d.ts +157 -0
- package/dist/types/core/clipboard.d.ts +48 -0
- package/dist/types/core/commands.d.ts +76 -0
- package/dist/types/core/comments.d.ts +22 -0
- package/dist/types/core/conditional.d.ts +114 -0
- package/dist/types/core/csv.d.ts +93 -0
- package/dist/types/core/data.d.ts +26 -0
- package/dist/types/core/document.d.ts +49 -0
- package/dist/types/core/extension.d.ts +177 -0
- package/dist/types/core/file.d.ts +301 -0
- package/dist/types/core/fill.d.ts +23 -0
- package/dist/types/core/find.d.ts +39 -0
- package/dist/types/core/format.d.ts +47 -0
- package/dist/types/core/group.d.ts +76 -0
- package/dist/types/core/history.d.ts +27 -0
- package/dist/types/core/index.d.ts +33 -0
- package/dist/types/core/link.d.ts +14 -0
- package/dist/types/core/merge.d.ts +26 -0
- package/dist/types/core/model.d.ts +363 -0
- package/dist/types/core/nav.d.ts +15 -0
- package/dist/types/core/paint.d.ts +19 -0
- package/dist/types/core/plain-cell-types.d.ts +6 -0
- package/dist/types/core/protect.d.ts +34 -0
- package/dist/types/core/selection.d.ts +25 -0
- package/dist/types/core/transaction.d.ts +470 -0
- package/dist/types/core/validation.d.ts +55 -0
- package/dist/types/core/versions.d.ts +38 -0
- package/dist/types/export/color.d.ts +7 -0
- package/dist/types/export/draw.d.ts +98 -0
- package/dist/types/export/index.d.ts +47 -0
- package/dist/types/formula/edit.d.ts +62 -0
- package/dist/types/formula/engine.d.ts +99 -0
- package/dist/types/formula/error.d.ts +30 -0
- package/dist/types/formula/functions.d.ts +62 -0
- package/dist/types/formula/help.d.ts +12 -0
- package/dist/types/formula/index.d.ts +13 -0
- package/dist/types/formula/parse.d.ts +92 -0
- package/dist/types/formula/values.d.ts +65 -0
- package/dist/types/formulas.d.ts +32 -0
- package/dist/types/index.d.ts +225 -0
- package/dist/types/locale.d.ts +531 -0
- package/dist/types/sheet.vue.d.ts +60 -0
- package/dist/types/types.d.ts +498 -0
- package/dist/types/use-comments.d.ts +47 -0
- package/dist/types/use-messages.d.ts +7 -0
- package/dist/use-comments.js +130 -0
- package/dist/use-comments.js.map +1 -0
- package/dist/use-messages.js +29 -0
- package/dist/use-messages.js.map +1 -0
- package/package.json +93 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-panel.js","names":["_sfc_main"],"sources":["../../src/components/date-panel.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { DouCalendar, type CalendarInstance } from 'dou-ui'\nimport { computed, useTemplateRef, watch } from 'vue'\nimport type { SheetCellPanelProps } from '../core/cell-type'\nimport { parseInput } from '../core/format'\nimport type { SheetValue } from '../core/model'\n\nconst props = defineProps<SheetCellPanelProps>()\nconst emit = defineEmits<{ pick: [value: SheetValue] }>()\nconst calendar = useTemplateRef<CalendarInstance>('calendar')\n\nconst DAY = /^(\\d{4}-\\d{2}-\\d{2})( .+)?$/\n\n/** The day in the cell, or in what is typed once it reads as one. */\nconst day = computed(() => {\n const value = props.typed ? parseInput(props.text).value : props.value\n return typeof value === 'string' ? (DAY.exec(value)?.[1] ?? null) : null\n})\nwatch(day, (value) => value && calendar.value?.show(value))\n\nfunction pick(value: unknown) {\n if (typeof value !== 'string') return\n // a time in the cell stays as it was\n const time = typeof props.value === 'string' ? (DAY.exec(props.value)?.[2] ?? '') : ''\n emit('pick', value + time)\n}\n</script>\n\n<template>\n <DouCalendar ref=\"calendar\" class=\"dou-sheet__calendar\" :model-value=\"day\" @select=\"pick\" />\n</template>\n"],"mappings":""}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { parseInput } from "../core/format.js";
|
|
2
|
+
import { DouCalendar } from "dou-ui";
|
|
3
|
+
import { computed, createBlock, defineComponent, openBlock, unref, useTemplateRef, watch } from "vue";
|
|
4
|
+
// src/components/date-panel.vue?vue&type=script&setup=true&lang.ts
|
|
5
|
+
var date_panel_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
6
|
+
__name: "date-panel",
|
|
7
|
+
props: {
|
|
8
|
+
value: { type: [
|
|
9
|
+
String,
|
|
10
|
+
Number,
|
|
11
|
+
Boolean,
|
|
12
|
+
null
|
|
13
|
+
] },
|
|
14
|
+
text: {},
|
|
15
|
+
typed: { type: Boolean },
|
|
16
|
+
setup: {}
|
|
17
|
+
},
|
|
18
|
+
emits: ["pick"],
|
|
19
|
+
setup(__props, { emit: __emit }) {
|
|
20
|
+
const props = __props;
|
|
21
|
+
const emit = __emit;
|
|
22
|
+
const calendar = useTemplateRef("calendar");
|
|
23
|
+
const DAY = /^(\d{4}-\d{2}-\d{2})( .+)?$/;
|
|
24
|
+
/** The day in the cell, or in what is typed once it reads as one. */
|
|
25
|
+
const day = computed(() => {
|
|
26
|
+
const value = props.typed ? parseInput(props.text).value : props.value;
|
|
27
|
+
return typeof value === "string" ? DAY.exec(value)?.[1] ?? null : null;
|
|
28
|
+
});
|
|
29
|
+
watch(day, (value) => value && calendar.value?.show(value));
|
|
30
|
+
function pick(value) {
|
|
31
|
+
if (typeof value !== "string") return;
|
|
32
|
+
const time = typeof props.value === "string" ? DAY.exec(props.value)?.[2] ?? "" : "";
|
|
33
|
+
emit("pick", value + time);
|
|
34
|
+
}
|
|
35
|
+
return (_ctx, _cache) => {
|
|
36
|
+
return openBlock(), createBlock(unref(DouCalendar), {
|
|
37
|
+
ref_key: "calendar",
|
|
38
|
+
ref: calendar,
|
|
39
|
+
class: "dou-sheet__calendar",
|
|
40
|
+
"model-value": day.value,
|
|
41
|
+
onSelect: pick
|
|
42
|
+
}, null, 8, ["model-value"]);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
export { date_panel_vue_vue_type_script_setup_true_lang_default as default };
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=date-panel.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-panel.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createBlock","_unref"],"sources":["../../src/components/date-panel.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { DouCalendar, type CalendarInstance } from 'dou-ui'\nimport { computed, useTemplateRef, watch } from 'vue'\nimport type { SheetCellPanelProps } from '../core/cell-type'\nimport { parseInput } from '../core/format'\nimport type { SheetValue } from '../core/model'\n\nconst props = defineProps<SheetCellPanelProps>()\nconst emit = defineEmits<{ pick: [value: SheetValue] }>()\nconst calendar = useTemplateRef<CalendarInstance>('calendar')\n\nconst DAY = /^(\\d{4}-\\d{2}-\\d{2})( .+)?$/\n\n/** The day in the cell, or in what is typed once it reads as one. */\nconst day = computed(() => {\n const value = props.typed ? parseInput(props.text).value : props.value\n return typeof value === 'string' ? (DAY.exec(value)?.[1] ?? null) : null\n})\nwatch(day, (value) => value && calendar.value?.show(value))\n\nfunction pick(value: unknown) {\n if (typeof value !== 'string') return\n // a time in the cell stays as it was\n const time = typeof props.value === 'string' ? (DAY.exec(props.value)?.[2] ?? '') : ''\n emit('pick', value + time)\n}\n</script>\n\n<template>\n <DouCalendar ref=\"calendar\" class=\"dou-sheet__calendar\" :model-value=\"day\" @select=\"pick\" />\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;EAOA,MAAM,QAAQ;EACd,MAAM,OAAO;EACb,MAAM,WAAW,eAAiC,UAAU;EAE5D,MAAM,MAAM;;EAGZ,MAAM,MAAM,eAAe;GACzB,MAAM,QAAQ,MAAM,QAAQ,WAAW,MAAM,IAAI,CAAC,CAAC,QAAQ,MAAM;GACjE,OAAO,OAAO,UAAU,WAAY,IAAI,KAAK,KAAK,CAAC,GAAG,MAAM,OAAQ;EACtE,CAAC;EACD,MAAM,MAAM,UAAU,SAAS,SAAS,OAAO,KAAK,KAAK,CAAC;EAE1D,SAAS,KAAK,OAAgB;GAC5B,IAAI,OAAO,UAAU,UAAU;GAE/B,MAAM,OAAO,OAAO,MAAM,UAAU,WAAY,IAAI,KAAK,MAAM,KAAK,CAAC,GAAG,MAAM,KAAM;GACpF,KAAK,QAAQ,QAAQ,IAAI;EAC3B;;GAIE,OAAAA,UAAA,GAAAC,YAA4FC,MAAA,WAAA,GAAA;IAA3E,SAAA;IAAJ,KAAI;IAAW,OAAM;IAAuB,eAAa,IAAA;IAAM,UAAQ"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import filter_panel_vue_vue_type_script_setup_true_lang_default from "./filter-panel.vue_vue_type_script_setup_true_lang.js";
|
|
2
|
+
// src/components/filter-panel.vue
|
|
3
|
+
var filter_panel_default = filter_panel_vue_vue_type_script_setup_true_lang_default;
|
|
4
|
+
export { filter_panel_default as default };
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=filter-panel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-panel.js","names":["_sfc_main"],"sources":["../../src/components/filter-panel.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { SearchIcon, SortAscendingIcon, SortDescendingIcon } from '@dou-ui/icons'\nimport { DouButton, DouCheckbox, DouInput, DouSegmentedControl, DouSelect } from 'dou-ui'\nimport { computed, nextTick, onMounted, ref, shallowRef, useTemplateRef } from 'vue'\nimport type { SheetCondition, SheetFilterRule, SheetSortOrder } from '../core/model'\nimport { format, type SheetMessages } from '../locale'\n\n/*\n * What a filter button opens: sorting by the column, and which of its\n * values (or which condition) the rows must have to stay shown.\n */\n\nconst props = defineProps<{\n /** The column's values as they show, each once, in sorted order; `''` is empty. */\n values: readonly string[]\n /** Whether most of the column's values are numbers: conditions then start at “≥”. */\n numeric?: boolean\n rule: SheetFilterRule | undefined\n sorted: SheetSortOrder | undefined\n messages: SheetMessages\n readonly: boolean\n}>()\nconst emit = defineEmits<{\n sort: [order: SheetSortOrder]\n apply: [rule: SheetFilterRule | undefined]\n}>()\n\nconst m = computed(() => props.messages.filter)\n\n/** At most this many values are listed; searching finds the rest. */\nconst LIMIT = 500\n\ntype Mode = 'values' | 'condition'\nconst mode = ref<Mode>(props.rule && 'condition' in props.rule ? 'condition' : 'values')\n\n// values ------------------------------------------------------------------------------------\n\nconst search = ref('')\nconst shown = computed(() => {\n const q = search.value.trim().toLocaleLowerCase()\n const all = q\n ? props.values.filter((v) => (v || m.value.blank).toLocaleLowerCase().includes(q))\n : props.values\n return { list: all.slice(0, LIMIT), more: Math.max(0, all.length - LIMIT), all }\n})\n/** The values kept; all of them when the column has no values rule. */\nconst kept = shallowRef(\n new Set(props.rule && 'values' in props.rule ? props.rule.values : props.values),\n)\nconst allKept = computed(() => shown.value.all.every((v) => kept.value.has(v)))\nconst someKept = computed(() => shown.value.all.some((v) => kept.value.has(v)))\nfunction toggle(value: string, on: boolean) {\n const next = new Set(kept.value)\n if (on) next.add(value)\n else next.delete(value)\n kept.value = next\n}\nfunction toggleAll(on: boolean) {\n const next = new Set(kept.value)\n for (const v of shown.value.all) {\n if (on) next.add(v)\n else next.delete(v)\n }\n kept.value = next\n}\n\n// condition ---------------------------------------------------------------------------------\n\nconst OPS: SheetCondition['op'][] = [\n 'eq',\n 'ne',\n 'gt',\n 'ge',\n 'lt',\n 'le',\n 'contains',\n 'notContains',\n 'begins',\n 'ends',\n 'blank',\n 'notBlank',\n]\nconst condition = props.rule && 'condition' in props.rule ? props.rule.condition : undefined\nconst op = ref<SheetCondition['op']>(condition?.op ?? (props.numeric ? 'ge' : 'contains'))\nconst operand = ref(condition?.value == null ? '' : String(condition.value))\nconst needsOperand = computed(() => op.value !== 'blank' && op.value !== 'notBlank')\nconst opOptions = computed(() => OPS.map((value) => ({ value, label: props.messages.ops[value] })))\n\n// result ------------------------------------------------------------------------------------\n\n/** The rule the panel would set; `undefined` keeps every row. */\nconst result = computed<SheetFilterRule | undefined>(() => {\n if (mode.value === 'condition') {\n if (!needsOperand.value) return { condition: { op: op.value } }\n const text = operand.value.trim()\n if (!text) return undefined\n const n = Number(text)\n return { condition: { op: op.value, value: Number.isFinite(n) && /\\d/.test(text) ? n : text } }\n }\n // searched: what is kept among the matches; otherwise everything kept\n const list = search.value.trim() ? shown.value.all : props.values\n const values = list.filter((v) => kept.value.has(v))\n return values.length === props.values.length ? undefined : { values }\n})\nconst empty = computed(\n () =>\n mode.value === 'values' &&\n result.value &&\n 'values' in result.value &&\n !result.value.values.length,\n)\n\nconst label = (value: string) => value || m.value.blank\n\n// focus -------------------------------------------------------------------------------------\n\nconst root = useTemplateRef<HTMLElement>('root')\nconst TABBABLE =\n 'button:not(:disabled), input:not(:disabled), [tabindex]:not([tabindex=\"-1\"]):not(:disabled)'\nconst tabbable = () =>\n [...(root.value?.querySelectorAll<HTMLElement>(TABBABLE) ?? [])].filter(\n (el) => el.offsetParent !== null || el === document.activeElement,\n )\n\n// Opened from the button or Alt+↓, focus starts inside so the keyboard\n// can reach the panel; Escape hands it back to where it came from.\nonMounted(() => nextTick(() => tabbable()[0]?.focus({ preventScroll: true })))\n\n/** Keeps Tab inside the panel, going round from the last item to the first. */\nfunction onKeydown(event: KeyboardEvent) {\n if (event.key !== 'Tab') return\n const items = tabbable()\n if (!items.length) return\n const first = items[0]!\n const last = items[items.length - 1]!\n const at = document.activeElement\n if (event.shiftKey ? at === first || !root.value?.contains(at) : at === last) {\n event.preventDefault()\n event.stopPropagation()\n ;(event.shiftKey ? last : first).focus()\n }\n}\n</script>\n\n<template>\n <div ref=\"root\" class=\"dou-sheet-filter\" @keydown=\"onKeydown\">\n <div class=\"dou-sheet-filter__sort\" role=\"group\" :aria-label=\"m.sort\">\n <DouButton\n variant=\"ghost\"\n size=\"sm\"\n :disabled=\"readonly\"\n :aria-pressed=\"sorted === 'asc'\"\n @click=\"emit('sort', 'asc')\"\n >\n <template #leading><SortAscendingIcon /></template>\n {{ m.sortAsc }}\n </DouButton>\n <DouButton\n variant=\"ghost\"\n size=\"sm\"\n :disabled=\"readonly\"\n :aria-pressed=\"sorted === 'desc'\"\n @click=\"emit('sort', 'desc')\"\n >\n <template #leading><SortDescendingIcon /></template>\n {{ m.sortDesc }}\n </DouButton>\n </div>\n <DouSegmentedControl\n v-model=\"mode\"\n size=\"sm\"\n block\n :options=\"[\n { value: 'values', label: m.byValues },\n { value: 'condition', label: m.byCondition },\n ]\"\n />\n <template v-if=\"mode === 'values'\">\n <DouInput v-model=\"search\" size=\"sm\" :placeholder=\"m.search\" :aria-label=\"m.search\" clearable>\n <template #prefix><SearchIcon /></template>\n </DouInput>\n <div class=\"dou-sheet-filter__list\" role=\"group\" :aria-label=\"m.values\">\n <DouCheckbox\n v-if=\"shown.all.length\"\n class=\"dou-sheet-filter__item is-all\"\n :model-value=\"allKept\"\n :indeterminate=\"someKept && !allKept\"\n @update:model-value=\"(on: boolean) => toggleAll(on)\"\n >\n {{ search.trim() ? m.allMatches : m.all }}\n </DouCheckbox>\n <DouCheckbox\n v-for=\"value in shown.list\"\n :key=\"value\"\n class=\"dou-sheet-filter__item\"\n :class=\"{ 'is-blank': !value }\"\n :model-value=\"kept.has(value)\"\n @update:model-value=\"(on: boolean) => toggle(value, on)\"\n >\n {{ label(value) }}\n </DouCheckbox>\n <div v-if=\"!shown.all.length\" class=\"dou-sheet-filter__hint\">{{ m.noMatch }}</div>\n <div v-else-if=\"shown.more\" class=\"dou-sheet-filter__hint\">\n {{ format(m.more, { count: shown.more }) }}\n </div>\n </div>\n </template>\n <div v-else class=\"dou-sheet-filter__condition\">\n <DouSelect v-model=\"op\" size=\"sm\" :options=\"opOptions\" :aria-label=\"m.condition\" />\n <DouInput\n v-if=\"needsOperand\"\n v-model=\"operand\"\n size=\"sm\"\n :placeholder=\"m.value\"\n :aria-label=\"m.value\"\n @keydown.enter=\"emit('apply', result)\"\n />\n </div>\n <div class=\"dou-sheet-filter__footer\">\n <DouButton\n variant=\"ghost\"\n size=\"sm\"\n :disabled=\"!rule || readonly\"\n @click=\"emit('apply', undefined)\"\n >\n {{ m.clear }}\n </DouButton>\n <DouButton\n variant=\"solid\"\n tone=\"primary\"\n size=\"sm\"\n :disabled=\"!!empty || readonly\"\n @click=\"emit('apply', result)\"\n >\n {{ m.apply }}\n </DouButton>\n </div>\n </div>\n</template>\n"],"mappings":""}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { format } from "../locale.js";
|
|
2
|
+
import { DouButton, DouCheckbox, DouInput, DouSegmentedControl, DouSelect } from "dou-ui";
|
|
3
|
+
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, nextTick, normalizeClass, onMounted, openBlock, ref, renderList, shallowRef, toDisplayString, unref, useTemplateRef, withCtx, withKeys } from "vue";
|
|
4
|
+
import { SearchIcon, SortAscendingIcon, SortDescendingIcon } from "@dou-ui/icons";
|
|
5
|
+
// src/components/filter-panel.vue?vue&type=script&setup=true&lang.ts
|
|
6
|
+
var _hoisted_1 = ["aria-label"];
|
|
7
|
+
var _hoisted_2 = ["aria-label"];
|
|
8
|
+
var _hoisted_3 = {
|
|
9
|
+
key: 1,
|
|
10
|
+
class: "dou-sheet-filter__hint"
|
|
11
|
+
};
|
|
12
|
+
var _hoisted_4 = {
|
|
13
|
+
key: 2,
|
|
14
|
+
class: "dou-sheet-filter__hint"
|
|
15
|
+
};
|
|
16
|
+
var _hoisted_5 = {
|
|
17
|
+
key: 1,
|
|
18
|
+
class: "dou-sheet-filter__condition"
|
|
19
|
+
};
|
|
20
|
+
var _hoisted_6 = { class: "dou-sheet-filter__footer" };
|
|
21
|
+
var LIMIT = 500;
|
|
22
|
+
var TABBABLE = "button:not(:disabled), input:not(:disabled), [tabindex]:not([tabindex=\"-1\"]):not(:disabled)";
|
|
23
|
+
var filter_panel_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
24
|
+
__name: "filter-panel",
|
|
25
|
+
props: {
|
|
26
|
+
values: {},
|
|
27
|
+
numeric: { type: Boolean },
|
|
28
|
+
rule: {},
|
|
29
|
+
sorted: {},
|
|
30
|
+
messages: {},
|
|
31
|
+
readonly: { type: Boolean }
|
|
32
|
+
},
|
|
33
|
+
emits: ["sort", "apply"],
|
|
34
|
+
setup(__props, { emit: __emit }) {
|
|
35
|
+
const props = __props;
|
|
36
|
+
const emit = __emit;
|
|
37
|
+
const m = computed(() => props.messages.filter);
|
|
38
|
+
/** At most this many values are listed; searching finds the rest. */
|
|
39
|
+
const mode = ref(props.rule && "condition" in props.rule ? "condition" : "values");
|
|
40
|
+
const search = ref("");
|
|
41
|
+
const shown = computed(() => {
|
|
42
|
+
const q = search.value.trim().toLocaleLowerCase();
|
|
43
|
+
const all = q ? props.values.filter((v) => (v || m.value.blank).toLocaleLowerCase().includes(q)) : props.values;
|
|
44
|
+
return {
|
|
45
|
+
list: all.slice(0, LIMIT),
|
|
46
|
+
more: Math.max(0, all.length - LIMIT),
|
|
47
|
+
all
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
/** The values kept; all of them when the column has no values rule. */
|
|
51
|
+
const kept = shallowRef(new Set(props.rule && "values" in props.rule ? props.rule.values : props.values));
|
|
52
|
+
const allKept = computed(() => shown.value.all.every((v) => kept.value.has(v)));
|
|
53
|
+
const someKept = computed(() => shown.value.all.some((v) => kept.value.has(v)));
|
|
54
|
+
function toggle(value, on) {
|
|
55
|
+
const next = new Set(kept.value);
|
|
56
|
+
if (on) next.add(value);
|
|
57
|
+
else next.delete(value);
|
|
58
|
+
kept.value = next;
|
|
59
|
+
}
|
|
60
|
+
function toggleAll(on) {
|
|
61
|
+
const next = new Set(kept.value);
|
|
62
|
+
for (const v of shown.value.all) if (on) next.add(v);
|
|
63
|
+
else next.delete(v);
|
|
64
|
+
kept.value = next;
|
|
65
|
+
}
|
|
66
|
+
const OPS = [
|
|
67
|
+
"eq",
|
|
68
|
+
"ne",
|
|
69
|
+
"gt",
|
|
70
|
+
"ge",
|
|
71
|
+
"lt",
|
|
72
|
+
"le",
|
|
73
|
+
"contains",
|
|
74
|
+
"notContains",
|
|
75
|
+
"begins",
|
|
76
|
+
"ends",
|
|
77
|
+
"blank",
|
|
78
|
+
"notBlank"
|
|
79
|
+
];
|
|
80
|
+
const condition = props.rule && "condition" in props.rule ? props.rule.condition : void 0;
|
|
81
|
+
const op = ref(condition?.op ?? (props.numeric ? "ge" : "contains"));
|
|
82
|
+
const operand = ref(condition?.value == null ? "" : String(condition.value));
|
|
83
|
+
const needsOperand = computed(() => op.value !== "blank" && op.value !== "notBlank");
|
|
84
|
+
const opOptions = computed(() => OPS.map((value) => ({
|
|
85
|
+
value,
|
|
86
|
+
label: props.messages.ops[value]
|
|
87
|
+
})));
|
|
88
|
+
/** The rule the panel would set; `undefined` keeps every row. */
|
|
89
|
+
const result = computed(() => {
|
|
90
|
+
if (mode.value === "condition") {
|
|
91
|
+
if (!needsOperand.value) return { condition: { op: op.value } };
|
|
92
|
+
const text = operand.value.trim();
|
|
93
|
+
if (!text) return void 0;
|
|
94
|
+
const n = Number(text);
|
|
95
|
+
return { condition: {
|
|
96
|
+
op: op.value,
|
|
97
|
+
value: Number.isFinite(n) && /\d/.test(text) ? n : text
|
|
98
|
+
} };
|
|
99
|
+
}
|
|
100
|
+
const values = (search.value.trim() ? shown.value.all : props.values).filter((v) => kept.value.has(v));
|
|
101
|
+
return values.length === props.values.length ? void 0 : { values };
|
|
102
|
+
});
|
|
103
|
+
const empty = computed(() => mode.value === "values" && result.value && "values" in result.value && !result.value.values.length);
|
|
104
|
+
const label = (value) => value || m.value.blank;
|
|
105
|
+
const root = useTemplateRef("root");
|
|
106
|
+
const tabbable = () => [...root.value?.querySelectorAll(TABBABLE) ?? []].filter((el) => el.offsetParent !== null || el === document.activeElement);
|
|
107
|
+
onMounted(() => nextTick(() => tabbable()[0]?.focus({ preventScroll: true })));
|
|
108
|
+
/** Keeps Tab inside the panel, going round from the last item to the first. */
|
|
109
|
+
function onKeydown(event) {
|
|
110
|
+
if (event.key !== "Tab") return;
|
|
111
|
+
const items = tabbable();
|
|
112
|
+
if (!items.length) return;
|
|
113
|
+
const first = items[0];
|
|
114
|
+
const last = items[items.length - 1];
|
|
115
|
+
const at = document.activeElement;
|
|
116
|
+
if (event.shiftKey ? at === first || !root.value?.contains(at) : at === last) {
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
event.stopPropagation();
|
|
119
|
+
(event.shiftKey ? last : first).focus();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return (_ctx, _cache) => {
|
|
123
|
+
return openBlock(), createElementBlock("div", {
|
|
124
|
+
ref_key: "root",
|
|
125
|
+
ref: root,
|
|
126
|
+
class: "dou-sheet-filter",
|
|
127
|
+
onKeydown
|
|
128
|
+
}, [
|
|
129
|
+
createElementVNode("div", {
|
|
130
|
+
class: "dou-sheet-filter__sort",
|
|
131
|
+
role: "group",
|
|
132
|
+
"aria-label": m.value.sort
|
|
133
|
+
}, [createVNode(unref(DouButton), {
|
|
134
|
+
variant: "ghost",
|
|
135
|
+
size: "sm",
|
|
136
|
+
disabled: __props.readonly,
|
|
137
|
+
"aria-pressed": __props.sorted === "asc",
|
|
138
|
+
onClick: _cache[0] || (_cache[0] = ($event) => emit("sort", "asc"))
|
|
139
|
+
}, {
|
|
140
|
+
leading: withCtx(() => [createVNode(unref(SortAscendingIcon))]),
|
|
141
|
+
default: withCtx(() => [createTextVNode(" " + toDisplayString(m.value.sortAsc), 1)]),
|
|
142
|
+
_: 1
|
|
143
|
+
}, 8, ["disabled", "aria-pressed"]), createVNode(unref(DouButton), {
|
|
144
|
+
variant: "ghost",
|
|
145
|
+
size: "sm",
|
|
146
|
+
disabled: __props.readonly,
|
|
147
|
+
"aria-pressed": __props.sorted === "desc",
|
|
148
|
+
onClick: _cache[1] || (_cache[1] = ($event) => emit("sort", "desc"))
|
|
149
|
+
}, {
|
|
150
|
+
leading: withCtx(() => [createVNode(unref(SortDescendingIcon))]),
|
|
151
|
+
default: withCtx(() => [createTextVNode(" " + toDisplayString(m.value.sortDesc), 1)]),
|
|
152
|
+
_: 1
|
|
153
|
+
}, 8, ["disabled", "aria-pressed"])], 8, _hoisted_1),
|
|
154
|
+
createVNode(unref(DouSegmentedControl), {
|
|
155
|
+
modelValue: mode.value,
|
|
156
|
+
"onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => mode.value = $event),
|
|
157
|
+
size: "sm",
|
|
158
|
+
block: "",
|
|
159
|
+
options: [{
|
|
160
|
+
value: "values",
|
|
161
|
+
label: m.value.byValues
|
|
162
|
+
}, {
|
|
163
|
+
value: "condition",
|
|
164
|
+
label: m.value.byCondition
|
|
165
|
+
}]
|
|
166
|
+
}, null, 8, ["modelValue", "options"]),
|
|
167
|
+
mode.value === "values" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createVNode(unref(DouInput), {
|
|
168
|
+
modelValue: search.value,
|
|
169
|
+
"onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => search.value = $event),
|
|
170
|
+
size: "sm",
|
|
171
|
+
placeholder: m.value.search,
|
|
172
|
+
"aria-label": m.value.search,
|
|
173
|
+
clearable: ""
|
|
174
|
+
}, {
|
|
175
|
+
prefix: withCtx(() => [createVNode(unref(SearchIcon))]),
|
|
176
|
+
_: 1
|
|
177
|
+
}, 8, [
|
|
178
|
+
"modelValue",
|
|
179
|
+
"placeholder",
|
|
180
|
+
"aria-label"
|
|
181
|
+
]), createElementVNode("div", {
|
|
182
|
+
class: "dou-sheet-filter__list",
|
|
183
|
+
role: "group",
|
|
184
|
+
"aria-label": m.value.values
|
|
185
|
+
}, [
|
|
186
|
+
shown.value.all.length ? (openBlock(), createBlock(unref(DouCheckbox), {
|
|
187
|
+
key: 0,
|
|
188
|
+
class: "dou-sheet-filter__item is-all",
|
|
189
|
+
"model-value": allKept.value,
|
|
190
|
+
indeterminate: someKept.value && !allKept.value,
|
|
191
|
+
"onUpdate:modelValue": _cache[4] || (_cache[4] = (on) => toggleAll(on))
|
|
192
|
+
}, {
|
|
193
|
+
default: withCtx(() => [createTextVNode(toDisplayString(search.value.trim() ? m.value.allMatches : m.value.all), 1)]),
|
|
194
|
+
_: 1
|
|
195
|
+
}, 8, ["model-value", "indeterminate"])) : createCommentVNode("", true),
|
|
196
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(shown.value.list, (value) => {
|
|
197
|
+
return openBlock(), createBlock(unref(DouCheckbox), {
|
|
198
|
+
key: value,
|
|
199
|
+
class: normalizeClass(["dou-sheet-filter__item", { "is-blank": !value }]),
|
|
200
|
+
"model-value": kept.value.has(value),
|
|
201
|
+
"onUpdate:modelValue": (on) => toggle(value, on)
|
|
202
|
+
}, {
|
|
203
|
+
default: withCtx(() => [createTextVNode(toDisplayString(label(value)), 1)]),
|
|
204
|
+
_: 2
|
|
205
|
+
}, 1032, [
|
|
206
|
+
"class",
|
|
207
|
+
"model-value",
|
|
208
|
+
"onUpdate:modelValue"
|
|
209
|
+
]);
|
|
210
|
+
}), 128)),
|
|
211
|
+
!shown.value.all.length ? (openBlock(), createElementBlock("div", _hoisted_3, toDisplayString(m.value.noMatch), 1)) : shown.value.more ? (openBlock(), createElementBlock("div", _hoisted_4, toDisplayString(unref(format)(m.value.more, { count: shown.value.more })), 1)) : createCommentVNode("", true)
|
|
212
|
+
], 8, _hoisted_2)], 64)) : (openBlock(), createElementBlock("div", _hoisted_5, [createVNode(unref(DouSelect), {
|
|
213
|
+
modelValue: op.value,
|
|
214
|
+
"onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => op.value = $event),
|
|
215
|
+
size: "sm",
|
|
216
|
+
options: opOptions.value,
|
|
217
|
+
"aria-label": m.value.condition
|
|
218
|
+
}, null, 8, [
|
|
219
|
+
"modelValue",
|
|
220
|
+
"options",
|
|
221
|
+
"aria-label"
|
|
222
|
+
]), needsOperand.value ? (openBlock(), createBlock(unref(DouInput), {
|
|
223
|
+
key: 0,
|
|
224
|
+
modelValue: operand.value,
|
|
225
|
+
"onUpdate:modelValue": _cache[6] || (_cache[6] = ($event) => operand.value = $event),
|
|
226
|
+
size: "sm",
|
|
227
|
+
placeholder: m.value.value,
|
|
228
|
+
"aria-label": m.value.value,
|
|
229
|
+
onKeydown: _cache[7] || (_cache[7] = withKeys(($event) => emit("apply", result.value), ["enter"]))
|
|
230
|
+
}, null, 8, [
|
|
231
|
+
"modelValue",
|
|
232
|
+
"placeholder",
|
|
233
|
+
"aria-label"
|
|
234
|
+
])) : createCommentVNode("", true)])),
|
|
235
|
+
createElementVNode("div", _hoisted_6, [createVNode(unref(DouButton), {
|
|
236
|
+
variant: "ghost",
|
|
237
|
+
size: "sm",
|
|
238
|
+
disabled: !__props.rule || __props.readonly,
|
|
239
|
+
onClick: _cache[8] || (_cache[8] = ($event) => emit("apply", void 0))
|
|
240
|
+
}, {
|
|
241
|
+
default: withCtx(() => [createTextVNode(toDisplayString(m.value.clear), 1)]),
|
|
242
|
+
_: 1
|
|
243
|
+
}, 8, ["disabled"]), createVNode(unref(DouButton), {
|
|
244
|
+
variant: "solid",
|
|
245
|
+
tone: "primary",
|
|
246
|
+
size: "sm",
|
|
247
|
+
disabled: !!empty.value || __props.readonly,
|
|
248
|
+
onClick: _cache[9] || (_cache[9] = ($event) => emit("apply", result.value))
|
|
249
|
+
}, {
|
|
250
|
+
default: withCtx(() => [createTextVNode(toDisplayString(m.value.apply), 1)]),
|
|
251
|
+
_: 1
|
|
252
|
+
}, 8, ["disabled"])])
|
|
253
|
+
], 544);
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
export { filter_panel_vue_vue_type_script_setup_true_lang_default as default };
|
|
258
|
+
|
|
259
|
+
//# sourceMappingURL=filter-panel.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-panel.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_createElementVNode","_createVNode","_unref","_withCtx","_createTextVNode","_toDisplayString","_Fragment","_createBlock","_createCommentVNode","_renderList","_normalizeClass","_withKeys"],"sources":["../../src/components/filter-panel.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { SearchIcon, SortAscendingIcon, SortDescendingIcon } from '@dou-ui/icons'\nimport { DouButton, DouCheckbox, DouInput, DouSegmentedControl, DouSelect } from 'dou-ui'\nimport { computed, nextTick, onMounted, ref, shallowRef, useTemplateRef } from 'vue'\nimport type { SheetCondition, SheetFilterRule, SheetSortOrder } from '../core/model'\nimport { format, type SheetMessages } from '../locale'\n\n/*\n * What a filter button opens: sorting by the column, and which of its\n * values (or which condition) the rows must have to stay shown.\n */\n\nconst props = defineProps<{\n /** The column's values as they show, each once, in sorted order; `''` is empty. */\n values: readonly string[]\n /** Whether most of the column's values are numbers: conditions then start at “≥”. */\n numeric?: boolean\n rule: SheetFilterRule | undefined\n sorted: SheetSortOrder | undefined\n messages: SheetMessages\n readonly: boolean\n}>()\nconst emit = defineEmits<{\n sort: [order: SheetSortOrder]\n apply: [rule: SheetFilterRule | undefined]\n}>()\n\nconst m = computed(() => props.messages.filter)\n\n/** At most this many values are listed; searching finds the rest. */\nconst LIMIT = 500\n\ntype Mode = 'values' | 'condition'\nconst mode = ref<Mode>(props.rule && 'condition' in props.rule ? 'condition' : 'values')\n\n// values ------------------------------------------------------------------------------------\n\nconst search = ref('')\nconst shown = computed(() => {\n const q = search.value.trim().toLocaleLowerCase()\n const all = q\n ? props.values.filter((v) => (v || m.value.blank).toLocaleLowerCase().includes(q))\n : props.values\n return { list: all.slice(0, LIMIT), more: Math.max(0, all.length - LIMIT), all }\n})\n/** The values kept; all of them when the column has no values rule. */\nconst kept = shallowRef(\n new Set(props.rule && 'values' in props.rule ? props.rule.values : props.values),\n)\nconst allKept = computed(() => shown.value.all.every((v) => kept.value.has(v)))\nconst someKept = computed(() => shown.value.all.some((v) => kept.value.has(v)))\nfunction toggle(value: string, on: boolean) {\n const next = new Set(kept.value)\n if (on) next.add(value)\n else next.delete(value)\n kept.value = next\n}\nfunction toggleAll(on: boolean) {\n const next = new Set(kept.value)\n for (const v of shown.value.all) {\n if (on) next.add(v)\n else next.delete(v)\n }\n kept.value = next\n}\n\n// condition ---------------------------------------------------------------------------------\n\nconst OPS: SheetCondition['op'][] = [\n 'eq',\n 'ne',\n 'gt',\n 'ge',\n 'lt',\n 'le',\n 'contains',\n 'notContains',\n 'begins',\n 'ends',\n 'blank',\n 'notBlank',\n]\nconst condition = props.rule && 'condition' in props.rule ? props.rule.condition : undefined\nconst op = ref<SheetCondition['op']>(condition?.op ?? (props.numeric ? 'ge' : 'contains'))\nconst operand = ref(condition?.value == null ? '' : String(condition.value))\nconst needsOperand = computed(() => op.value !== 'blank' && op.value !== 'notBlank')\nconst opOptions = computed(() => OPS.map((value) => ({ value, label: props.messages.ops[value] })))\n\n// result ------------------------------------------------------------------------------------\n\n/** The rule the panel would set; `undefined` keeps every row. */\nconst result = computed<SheetFilterRule | undefined>(() => {\n if (mode.value === 'condition') {\n if (!needsOperand.value) return { condition: { op: op.value } }\n const text = operand.value.trim()\n if (!text) return undefined\n const n = Number(text)\n return { condition: { op: op.value, value: Number.isFinite(n) && /\\d/.test(text) ? n : text } }\n }\n // searched: what is kept among the matches; otherwise everything kept\n const list = search.value.trim() ? shown.value.all : props.values\n const values = list.filter((v) => kept.value.has(v))\n return values.length === props.values.length ? undefined : { values }\n})\nconst empty = computed(\n () =>\n mode.value === 'values' &&\n result.value &&\n 'values' in result.value &&\n !result.value.values.length,\n)\n\nconst label = (value: string) => value || m.value.blank\n\n// focus -------------------------------------------------------------------------------------\n\nconst root = useTemplateRef<HTMLElement>('root')\nconst TABBABLE =\n 'button:not(:disabled), input:not(:disabled), [tabindex]:not([tabindex=\"-1\"]):not(:disabled)'\nconst tabbable = () =>\n [...(root.value?.querySelectorAll<HTMLElement>(TABBABLE) ?? [])].filter(\n (el) => el.offsetParent !== null || el === document.activeElement,\n )\n\n// Opened from the button or Alt+↓, focus starts inside so the keyboard\n// can reach the panel; Escape hands it back to where it came from.\nonMounted(() => nextTick(() => tabbable()[0]?.focus({ preventScroll: true })))\n\n/** Keeps Tab inside the panel, going round from the last item to the first. */\nfunction onKeydown(event: KeyboardEvent) {\n if (event.key !== 'Tab') return\n const items = tabbable()\n if (!items.length) return\n const first = items[0]!\n const last = items[items.length - 1]!\n const at = document.activeElement\n if (event.shiftKey ? at === first || !root.value?.contains(at) : at === last) {\n event.preventDefault()\n event.stopPropagation()\n ;(event.shiftKey ? last : first).focus()\n }\n}\n</script>\n\n<template>\n <div ref=\"root\" class=\"dou-sheet-filter\" @keydown=\"onKeydown\">\n <div class=\"dou-sheet-filter__sort\" role=\"group\" :aria-label=\"m.sort\">\n <DouButton\n variant=\"ghost\"\n size=\"sm\"\n :disabled=\"readonly\"\n :aria-pressed=\"sorted === 'asc'\"\n @click=\"emit('sort', 'asc')\"\n >\n <template #leading><SortAscendingIcon /></template>\n {{ m.sortAsc }}\n </DouButton>\n <DouButton\n variant=\"ghost\"\n size=\"sm\"\n :disabled=\"readonly\"\n :aria-pressed=\"sorted === 'desc'\"\n @click=\"emit('sort', 'desc')\"\n >\n <template #leading><SortDescendingIcon /></template>\n {{ m.sortDesc }}\n </DouButton>\n </div>\n <DouSegmentedControl\n v-model=\"mode\"\n size=\"sm\"\n block\n :options=\"[\n { value: 'values', label: m.byValues },\n { value: 'condition', label: m.byCondition },\n ]\"\n />\n <template v-if=\"mode === 'values'\">\n <DouInput v-model=\"search\" size=\"sm\" :placeholder=\"m.search\" :aria-label=\"m.search\" clearable>\n <template #prefix><SearchIcon /></template>\n </DouInput>\n <div class=\"dou-sheet-filter__list\" role=\"group\" :aria-label=\"m.values\">\n <DouCheckbox\n v-if=\"shown.all.length\"\n class=\"dou-sheet-filter__item is-all\"\n :model-value=\"allKept\"\n :indeterminate=\"someKept && !allKept\"\n @update:model-value=\"(on: boolean) => toggleAll(on)\"\n >\n {{ search.trim() ? m.allMatches : m.all }}\n </DouCheckbox>\n <DouCheckbox\n v-for=\"value in shown.list\"\n :key=\"value\"\n class=\"dou-sheet-filter__item\"\n :class=\"{ 'is-blank': !value }\"\n :model-value=\"kept.has(value)\"\n @update:model-value=\"(on: boolean) => toggle(value, on)\"\n >\n {{ label(value) }}\n </DouCheckbox>\n <div v-if=\"!shown.all.length\" class=\"dou-sheet-filter__hint\">{{ m.noMatch }}</div>\n <div v-else-if=\"shown.more\" class=\"dou-sheet-filter__hint\">\n {{ format(m.more, { count: shown.more }) }}\n </div>\n </div>\n </template>\n <div v-else class=\"dou-sheet-filter__condition\">\n <DouSelect v-model=\"op\" size=\"sm\" :options=\"opOptions\" :aria-label=\"m.condition\" />\n <DouInput\n v-if=\"needsOperand\"\n v-model=\"operand\"\n size=\"sm\"\n :placeholder=\"m.value\"\n :aria-label=\"m.value\"\n @keydown.enter=\"emit('apply', result)\"\n />\n </div>\n <div class=\"dou-sheet-filter__footer\">\n <DouButton\n variant=\"ghost\"\n size=\"sm\"\n :disabled=\"!rule || readonly\"\n @click=\"emit('apply', undefined)\"\n >\n {{ m.clear }}\n </DouButton>\n <DouButton\n variant=\"solid\"\n tone=\"primary\"\n size=\"sm\"\n :disabled=\"!!empty || readonly\"\n @click=\"emit('apply', result)\"\n >\n {{ m.apply }}\n </DouButton>\n </div>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA8BA,IAAM,QAAQ;AAuFd,IAAM,WACJ;;;;;;;;;;;;;EA1GF,MAAM,QAAQ;EAUd,MAAM,OAAO;EAKb,MAAM,IAAI,eAAe,MAAM,SAAS,MAAM;;EAM9C,MAAM,OAAO,IAAU,MAAM,QAAQ,eAAe,MAAM,OAAO,cAAc,QAAQ;EAIvF,MAAM,SAAS,IAAI,EAAE;EACrB,MAAM,QAAQ,eAAe;GAC3B,MAAM,IAAI,OAAO,MAAM,KAAK,CAAC,CAAC,kBAAkB;GAChD,MAAM,MAAM,IACR,MAAM,OAAO,QAAQ,OAAO,KAAK,EAAE,MAAM,MAAA,CAAO,kBAAkB,CAAC,CAAC,SAAS,CAAC,CAAC,IAC/E,MAAM;GACV,OAAO;IAAE,MAAM,IAAI,MAAM,GAAG,KAAK;IAAG,MAAM,KAAK,IAAI,GAAG,IAAI,SAAS,KAAK;IAAG;GAAI;EACjF,CAAC;;EAED,MAAM,OAAO,WACX,IAAI,IAAI,MAAM,QAAQ,YAAY,MAAM,OAAO,MAAM,KAAK,SAAS,MAAM,MAAM,CACjF;EACA,MAAM,UAAU,eAAe,MAAM,MAAM,IAAI,OAAO,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC;EAC9E,MAAM,WAAW,eAAe,MAAM,MAAM,IAAI,MAAM,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC;EAC9E,SAAS,OAAO,OAAe,IAAa;GAC1C,MAAM,OAAO,IAAI,IAAI,KAAK,KAAK;GAC/B,IAAI,IAAI,KAAK,IAAI,KAAK;QACjB,KAAK,OAAO,KAAK;GACtB,KAAK,QAAQ;EACf;EACA,SAAS,UAAU,IAAa;GAC9B,MAAM,OAAO,IAAI,IAAI,KAAK,KAAK;GAC/B,KAAK,MAAM,KAAK,MAAM,MAAM,KAC1B,IAAI,IAAI,KAAK,IAAI,CAAC;QACb,KAAK,OAAO,CAAC;GAEpB,KAAK,QAAQ;EACf;EAIA,MAAM,MAA8B;GAClC;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,MAAM,YAAY,MAAM,QAAQ,eAAe,MAAM,OAAO,MAAM,KAAK,YAAY,KAAA;EACnF,MAAM,KAAK,IAA0B,WAAW,OAAO,MAAM,UAAU,OAAO,WAAW;EACzF,MAAM,UAAU,IAAI,WAAW,SAAS,OAAO,KAAK,OAAO,UAAU,KAAK,CAAC;EAC3E,MAAM,eAAe,eAAe,GAAG,UAAU,WAAW,GAAG,UAAU,UAAU;EACnF,MAAM,YAAY,eAAe,IAAI,KAAK,WAAW;GAAE;GAAO,OAAO,MAAM,SAAS,IAAI;EAAO,EAAE,CAAC;;EAKlG,MAAM,SAAS,eAA4C;GACzD,IAAI,KAAK,UAAU,aAAa;IAC9B,IAAI,CAAC,aAAa,OAAO,OAAO,EAAE,WAAW,EAAE,IAAI,GAAG,MAAM,EAAE;IAC9D,MAAM,OAAO,QAAQ,MAAM,KAAK;IAChC,IAAI,CAAC,MAAM,OAAO,KAAA;IAClB,MAAM,IAAI,OAAO,IAAI;IACrB,OAAO,EAAE,WAAW;KAAE,IAAI,GAAG;KAAO,OAAO,OAAO,SAAS,CAAC,KAAK,KAAK,KAAK,IAAI,IAAI,IAAI;IAAK,EAAE;GAChG;GAGA,MAAM,UADO,OAAO,MAAM,KAAK,IAAI,MAAM,MAAM,MAAM,MAAM,OAAA,CACvC,QAAQ,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC;GACnD,OAAO,OAAO,WAAW,MAAM,OAAO,SAAS,KAAA,IAAY,EAAE,OAAO;EACtE,CAAC;EACD,MAAM,QAAQ,eAEV,KAAK,UAAU,YACf,OAAO,SACP,YAAY,OAAO,SACnB,CAAC,OAAO,MAAM,OAAO,MACzB;EAEA,MAAM,SAAS,UAAkB,SAAS,EAAE,MAAM;EAIlD,MAAM,OAAO,eAA4B,MAAM;EAG/C,MAAM,iBACJ,CAAC,GAAI,KAAK,OAAO,iBAA8B,QAAQ,KAAK,CAAC,CAAE,CAAC,CAAC,QAC9D,OAAO,GAAG,iBAAiB,QAAQ,OAAO,SAAS,aACtD;EAIF,gBAAgB,eAAe,SAAS,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,eAAe,KAAK,CAAC,CAAC,CAAC;;EAG7E,SAAS,UAAU,OAAsB;GACvC,IAAI,MAAM,QAAQ,OAAO;GACzB,MAAM,QAAQ,SAAS;GACvB,IAAI,CAAC,MAAM,QAAQ;GACnB,MAAM,QAAQ,MAAM;GACpB,MAAM,OAAO,MAAM,MAAM,SAAS;GAClC,MAAM,KAAK,SAAS;GACpB,IAAI,MAAM,WAAW,OAAO,SAAS,CAAC,KAAK,OAAO,SAAS,EAAE,IAAI,OAAO,MAAM;IAC5E,MAAM,eAAe;IACrB,MAAM,gBAAgB;IACrB,CAAC,MAAM,WAAW,OAAO,MAAA,CAAO,MAAM;GACzC;EACF;;GAIE,OAAAA,UAAA,GAAAC,mBA4FM,OAAA;IA5FG,SAAA;IAAJ,KAAI;IAAO,OAAM;IAA6B;;IACjDC,mBAqBM,OAAA;KArBD,OAAM;KAAyB,MAAK;KAAS,cAAY,EAAA,MAAE;I,IAC9DC,YASYC,MAAA,SAAA,GAAA;KARV,SAAQ;KACR,MAAK;KACJ,UAAU,QAAA;KACV,gBAAc,QAAA,WAAM;KACpB,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,QAAA,KAAA;;KAED,SAAOC,cAAsB,CAArBF,YAAqBC,MAAA,iBAAA,CAAA,C;KAAW,SAAAC,cACnD,CADmDC,gBAAA,MACnDC,gBAAG,EAAA,MAAE,OAAO,GAAA,CAAA,C;;I,qCAEdJ,YASYC,MAAA,SAAA,GAAA;KARV,SAAQ;KACR,MAAK;KACJ,UAAU,QAAA;KACV,gBAAc,QAAA,WAAM;KACpB,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,QAAA,MAAA;;KAED,SAAOC,cAAuB,CAAtBF,YAAsBC,MAAA,kBAAA,CAAA,C;KAAW,SAAAC,cACpD,CADoDC,gBAAA,MACpDC,gBAAG,EAAA,MAAE,QAAQ,GAAA,CAAA,C;;;IAGjBJ,YAQEC,MAAA,mBAAA,GAAA;KAPS,YAAA,KAAA;KAAA,uBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,KAAI,QAAA;KACb,MAAK;KACL,OAAA;KACC,SAAO,CAAsC;MAAA,OAAA;MAAA,OAAA,EAAA,MAAE;KAAQ,GAAyC;MAAA,OAAA;MAAA,OAAA,EAAA,MAAE;KAAW,C;;IAKhG,KAAA,UAAI,YAApBJ,UAAA,GAAAC,mBA6BWO,UAAA,EAAA,KAAA,EAAA,GAAA,CA5BTL,YAEWC,MAAA,QAAA,GAAA;KAFQ,YAAA,OAAA;KAAA,uBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,OAAM,QAAA;KAAE,MAAK;KAAM,aAAa,EAAA,MAAE;KAAS,cAAY,EAAA,MAAE;KAAQ,WAAA;;KACvE,QAAMC,cAAe,CAAdF,YAAcC,MAAA,UAAA,CAAA,C;;;;;;I,IAElCF,mBAwBM,OAAA;KAxBD,OAAM;KAAyB,MAAK;KAAS,cAAY,EAAA,MAAE;;KAEtD,MAAA,MAAM,IAAI,UADlBF,UAAA,GAAAS,YAQcL,MAAA,WAAA,GAAA;;MANZ,OAAM;MACL,eAAa,QAAA;MACb,eAAe,SAAA,SAAQ,CAAK,QAAA;MAC5B,uBAAkB,OAAA,OAAA,OAAA,MAAG,OAAgB,UAAU,EAAE;;MAElD,SAAAC,cAA0C,CAAvCC,gBAAAC,gBAAA,OAAA,MAAO,KAAI,IAAK,EAAA,MAAE,aAAa,EAAA,MAAE,GAAG,GAAA,CAAA,C;;;MAEzCP,UAAA,IAAA,GAAAC,mBAScO,UAAA,MAAAG,WARI,MAAA,MAAM,OAAf,UAAK;MADd,OAAAX,UAAA,GAAAS,YAScL,MAAA,WAAA,GAAA;OAPX,KAAK;OACN,OAAKQ,eAAA,CAAC,0BAAwB,EAAA,YAAA,CACP,MAAK,CAAA,CAAA;OAC3B,eAAa,KAAA,MAAK,IAAI,KAAK;OAC3B,wBAAqB,OAAgB,OAAO,OAAO,EAAE;;OAEtD,SAAAP,cAAkB,CAAfC,gBAAAC,gBAAA,MAAM,KAAK,CAAA,GAAA,CAAA,C;;;;;;;;KAEJ,CAAA,MAAA,MAAM,IAAI,UAAtBP,UAAA,GAAAC,mBAAkF,OAAlF,YAAkFM,gBAAlB,EAAA,MAAE,OAAO,GAAA,CAAA,KACzD,MAAA,MAAM,QAAtBP,UAAA,GAAAC,mBAEM,OAFN,YAEMM,gBADDH,MAAA,MAAA,CAAM,CAAC,EAAA,MAAE,MAAI,EAAA,OAAW,MAAA,MAAM,KAAI,CAAA,CAAA,GAAA,CAAA,K;I,4BAI3CJ,UAAA,GAAAC,mBAUM,OAVN,YAUM,CATJE,YAAmFC,MAAA,SAAA,GAAA;KAA/D,YAAA,GAAA;KAAA,uBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,GAAE,QAAA;KAAE,MAAK;KAAM,SAAS,UAAA;KAAY,cAAY,EAAA,MAAE;;;;;I,IAE9D,aAAA,SADRJ,UAAA,GAAAS,YAOEL,MAAA,QAAA,GAAA;;KALS,YAAA,QAAA;KAAA,uBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,QAAO,QAAA;KAChB,MAAK;KACJ,aAAa,EAAA,MAAE;KACf,cAAY,EAAA,MAAE;KACd,WAAO,OAAA,OAAA,OAAA,KAAAS,UAAA,WAAQ,KAAI,SAAU,OAAA,KAAM,GAAA,CAAA,OAAA,CAAA;;;;;;IAGxCX,mBAkBM,OAlBN,YAkBM,CAjBJC,YAOYC,MAAA,SAAA,GAAA;KANV,SAAQ;KACR,MAAK;KACJ,UAAQ,CAAG,QAAA,QAAQ,QAAA;KACnB,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,SAAU,KAAA,CAAS;;KAE/B,SAAAC,cAAa,CAAVC,gBAAAC,gBAAA,EAAA,MAAE,KAAK,GAAA,CAAA,C;;I,qBAEZJ,YAQYC,MAAA,SAAA,GAAA;KAPV,SAAQ;KACR,MAAK;KACL,MAAK;KACJ,UAAQ,CAAA,CAAI,MAAA,SAAS,QAAA;KACrB,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,SAAU,OAAA,KAAM;;KAE5B,SAAAC,cAAa,CAAVC,gBAAAC,gBAAA,EAAA,MAAE,KAAK,GAAA,CAAA,C"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import format_dialog_vue_vue_type_script_setup_true_lang_default from "./format-dialog.vue_vue_type_script_setup_true_lang.js";
|
|
2
|
+
// src/components/format-dialog.vue
|
|
3
|
+
var format_dialog_default = format_dialog_vue_vue_type_script_setup_true_lang_default;
|
|
4
|
+
export { format_dialog_default as default };
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=format-dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-dialog.js","names":["_sfc_main"],"sources":["../../src/components/format-dialog.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n DouButton,\n DouCheckbox,\n DouDialog,\n DouForm,\n DouFormItem,\n DouInput,\n DouInputNumber,\n DouSegmentedControl,\n DouSelect,\n type Tone,\n} from 'dou-ui'\nimport { computed, ref, useTemplateRef, watch } from 'vue'\nimport { readRuleRange, ruleRangeText } from '../core/conditional'\nimport type {\n SheetColor,\n SheetCondition,\n SheetConditionalFormat,\n SheetConditionalStyle,\n SheetStyle,\n SheetValue,\n} from '../core/model'\nimport type { SheetMessages } from '../locale'\nimport {\n barSwatch,\n LOOKS,\n lookName,\n SCALES,\n scaleName,\n scaleSwatch,\n styleSample,\n TONES,\n toneKey,\n type SheetLookName,\n type SheetScaleColors,\n type SheetScaleName,\n} from './format-presets'\n\n/*\n * Adds or edits one conditional format: the cells it applies to, what\n * they must meet, and how they look then. It starts from `format`, which\n * the toolbar's menu fills in for a new rule.\n */\n\nconst props = defineProps<{\n format: SheetConditionalFormat\n title: string\n /** The workbook's named styles, for a rule that names one. */\n styles: Record<string, SheetStyle> | undefined\n messages: SheetMessages\n}>()\nconst open = defineModel<boolean>('open', { required: true })\nconst emit = defineEmits<{ apply: [format: SheetConditionalFormat]; closed: [] }>()\n\nconst m = computed(() => props.messages.formats)\n\ntype Kind = 'when' | 'formula' | 'duplicates' | 'rank' | 'average' | 'scale' | 'bar'\nfunction kindOf(f: SheetConditionalFormat): Kind {\n if ('when' in f) return 'when'\n if ('formula' in f) return 'formula'\n if ('duplicates' in f || 'unique' in f) return 'duplicates'\n if ('top' in f || 'bottom' in f) return 'rank'\n if ('average' in f) return 'average'\n return 'scale' in f ? 'scale' : 'bar'\n}\nconst kind = computed(() => kindOf(props.format))\n\nconst OPS: SheetCondition['op'][] = [\n 'gt',\n 'ge',\n 'lt',\n 'le',\n 'eq',\n 'ne',\n 'between',\n 'notBetween',\n 'contains',\n 'notContains',\n 'begins',\n 'ends',\n 'blank',\n 'notBlank',\n]\nconst opOptions = computed(() => OPS.map((value) => ({ value, label: props.messages.ops[value] })))\n\nconst target = ref('')\nconst op = ref<SheetCondition['op']>('gt')\nconst value = ref('')\nconst to = ref('')\nconst formula = ref('')\nconst count = ref<number | null>(10)\nconst percent = ref(false)\nconst repeated = ref<'repeated' | 'unique'>('repeated')\nconst look = ref<SheetLookName | 'current'>('danger')\nconst scale = ref<SheetScaleName | 'current'>('rag')\nconst bar = ref<Tone | 'current'>('neutral')\nconst tried = ref(false)\n\nconst text = (v: SheetValue | undefined) => (v == null ? '' : String(v))\n\n/** The rule's own look, when it isn't one the dialog offers. */\nconst current = computed<SheetConditionalStyle | undefined>(() => {\n const f = props.format\n if (!('style' in f)) return undefined\n const style = typeof f.style === 'string' ? undefined : f.style\n return style && lookName(style) ? undefined : f.style\n})\nconst resolve = (style: SheetConditionalStyle | undefined) =>\n typeof style === 'string' ? props.styles?.[style] : style\n\nwatch(open, (now) => {\n if (!now) return\n const f = props.format\n target.value = ruleRangeText(f.range)\n op.value = 'when' in f ? f.when.op : 'gt'\n value.value = 'when' in f ? text(f.when.value) : ''\n to.value = 'when' in f ? text(f.when.to) : ''\n formula.value = 'formula' in f ? f.formula : ''\n count.value = 'top' in f && f.top != null ? f.top : 'bottom' in f ? (f.bottom ?? 10) : 10\n percent.value = 'percent' in f && !!f.percent\n repeated.value = 'unique' in f ? 'unique' : 'repeated'\n look.value =\n 'style' in f\n ? ((typeof f.style === 'string' ? undefined : lookName(f.style)) ?? 'current')\n : 'danger'\n scale.value = 'scale' in f ? (scaleName(f.scale) ?? 'current') : 'rag'\n bar.value =\n 'bar' in f\n ? f.bar === true\n ? 'neutral'\n : TONES.includes(f.bar as Tone)\n ? (f.bar as Tone)\n : 'current'\n : 'neutral'\n tried.value = false\n})\n\nconst lookOptions = computed(() => [\n ...(current.value === undefined ? [] : [{ value: 'current', label: m.value.current }]),\n ...(Object.keys(LOOKS) as SheetLookName[]).map((name) => ({\n value: name,\n label: m.value.looks[name],\n })),\n])\nfunction lookSample(name: string) {\n return styleSample(name === 'current' ? resolve(current.value) : LOOKS[name as SheetLookName])\n}\n\nconst scaleOptions = computed(() => [\n ...('scale' in props.format && !scaleName(props.format.scale)\n ? [{ value: 'current', label: m.value.current }]\n : []),\n ...(Object.keys(SCALES) as SheetScaleName[]).map((name) => ({\n value: name,\n label: m.value.scales[name],\n })),\n])\nfunction scaleColors(name: string): readonly SheetColor[] {\n if (name !== 'current') return SCALES[name as SheetScaleName]\n return 'scale' in props.format ? props.format.scale : SCALES.rag\n}\n\nconst barOptions = computed(() => [\n ...('bar' in props.format &&\n props.format.bar !== true &&\n !TONES.includes(props.format.bar as Tone)\n ? [{ value: 'current', label: m.value.current }]\n : []),\n ...TONES.map((tone) => ({ value: tone, label: props.messages[toneKey(tone)] })),\n])\nfunction barOf(name: string): SheetColor {\n if (name !== 'current') return name\n return 'bar' in props.format && props.format.bar !== true ? props.format.bar : 'neutral'\n}\n\nconst needsValue = computed(\n () => kind.value === 'when' && op.value !== 'blank' && op.value !== 'notBlank',\n)\nconst needsTo = computed(() => op.value === 'between' || op.value === 'notBetween')\nconst rangeOk = computed(() => readRuleRange(target.value) != null)\nconst valueOk = computed(\n () =>\n !needsValue.value || (value.value.trim() !== '' && (!needsTo.value || to.value.trim() !== '')),\n)\nconst formulaOk = computed(\n () => kind.value !== 'formula' || formula.value.trim().replace(/^=/, '') !== '',\n)\nconst countOk = computed(() => kind.value !== 'rank' || (count.value ?? 0) >= 1)\n\n/** A typed operand: a number when it reads as one, otherwise the text. */\nfunction operand(input: string): SheetValue {\n const trimmed = input.trim()\n const n = Number(trimmed.replace(/,/g, ''))\n return trimmed && Number.isFinite(n) && /\\d/.test(trimmed) ? n : trimmed\n}\n\nfunction result(): SheetConditionalFormat {\n const f = props.format\n const range = readRuleRange(target.value)!\n const style: SheetConditionalStyle =\n look.value === 'current' && current.value !== undefined\n ? current.value\n : { ...LOOKS[look.value as SheetLookName] }\n switch (kind.value) {\n case 'when': {\n const when: SheetCondition = { op: op.value }\n if (needsValue.value) when.value = operand(value.value)\n if (needsValue.value && needsTo.value) when.to = operand(to.value)\n return { range, when, style }\n }\n case 'formula': {\n const body = formula.value.trim()\n return { range, formula: body.startsWith('=') ? body : `=${body}`, style }\n }\n case 'duplicates':\n return repeated.value === 'repeated'\n ? { range, duplicates: true, style }\n : { range, unique: true, style }\n case 'rank': {\n const n = Math.round(count.value!)\n const rank = 'bottom' in f && f.bottom != null ? { bottom: n } : { top: n }\n return { range, ...rank, ...(percent.value && { percent: true }), style }\n }\n case 'average':\n return { range, average: 'average' in f ? f.average : 'above', style }\n case 'scale':\n return { range, scale: [...scaleColors(scale.value)] as SheetScaleColors }\n case 'bar':\n return {\n range,\n bar: bar.value === 'neutral' && 'bar' in f && f.bar === true ? true : barOf(bar.value),\n }\n }\n}\n\n// the first thing to fill in takes the focus, not the close button\nconst rangeInput = useTemplateRef<InstanceType<typeof DouInput>>('rangeInput')\nconst valueInput = useTemplateRef<InstanceType<typeof DouInput>>('valueInput')\nconst formulaInput = useTemplateRef<InstanceType<typeof DouInput>>('formulaInput')\nconst countInput = useTemplateRef<InstanceType<typeof DouInputNumber>>('countInput')\nfunction focusFirst() {\n ;(valueInput.value ?? formulaInput.value ?? countInput.value ?? rangeInput.value)?.focus()\n}\n\nfunction apply() {\n tried.value = true\n if (!rangeOk.value || !valueOk.value || !formulaOk.value || !countOk.value) return\n emit('apply', result())\n open.value = false\n}\n</script>\n\n<template>\n <DouDialog\n v-model:open=\"open\"\n :title=\"title\"\n :width=\"400\"\n @opened=\"focusFirst\"\n @closed=\"emit('closed')\"\n >\n <DouForm layout=\"vertical\" class=\"dou-sheet-format\" @submit=\"apply\">\n <DouFormItem :label=\"m.range\" :error=\"tried && !rangeOk ? m.invalidRange : undefined\">\n <DouInput ref=\"rangeInput\" v-model=\"target\" :invalid=\"tried && !rangeOk\" />\n </DouFormItem>\n <template v-if=\"kind === 'when'\">\n <DouFormItem :label=\"m.condition\">\n <DouSelect v-model=\"op\" :options=\"opOptions\" />\n </DouFormItem>\n <DouFormItem\n v-if=\"needsValue\"\n :label=\"m.value\"\n :error=\"tried && !valueOk ? m.needValue : undefined\"\n >\n <div class=\"dou-sheet-format__values\">\n <DouInput\n ref=\"valueInput\"\n v-model=\"value\"\n :aria-label=\"m.value\"\n :invalid=\"tried && !value.trim()\"\n @keydown.enter.prevent=\"apply\"\n />\n <template v-if=\"needsTo\">\n <span class=\"dou-sheet-format__and\">{{ m.and }}</span>\n <DouInput\n v-model=\"to\"\n :aria-label=\"m.value\"\n :invalid=\"tried && !to.trim()\"\n @keydown.enter.prevent=\"apply\"\n />\n </template>\n </div>\n </DouFormItem>\n </template>\n <DouFormItem\n v-if=\"kind === 'formula'\"\n :label=\"m.formulaField\"\n :help=\"m.formulaHelp\"\n :error=\"tried && !formulaOk ? m.needFormula : undefined\"\n >\n <DouInput\n ref=\"formulaInput\"\n v-model=\"formula\"\n class=\"dou-sheet-format__formula\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n :invalid=\"tried && !formulaOk\"\n @keydown.enter.prevent=\"apply\"\n />\n </DouFormItem>\n <DouFormItem v-if=\"kind === 'rank'\" :label=\"m.count\">\n <div class=\"dou-sheet-format__values\">\n <DouInputNumber ref=\"countInput\" v-model=\"count\" :min=\"1\" :step=\"1\" :precision=\"0\" />\n <DouCheckbox v-model=\"percent\">{{ m.percent }}</DouCheckbox>\n </div>\n </DouFormItem>\n <DouSegmentedControl\n v-if=\"kind === 'duplicates'\"\n v-model=\"repeated\"\n block\n :options=\"[\n { value: 'repeated', label: m.repeated },\n { value: 'unique', label: m.unique },\n ]\"\n />\n <DouFormItem v-if=\"kind === 'scale'\" :label=\"m.color\">\n <DouSelect v-model=\"scale\" :options=\"scaleOptions\">\n <template #option=\"{ option }\">\n <span class=\"dou-sheet-format__look\">\n <span\n class=\"dou-sheet-format__sample\"\n :style=\"{ backgroundImage: scaleSwatch(scaleColors(String(option.value))) }\"\n aria-hidden=\"true\"\n />\n {{ option.label }}\n </span>\n </template>\n <template #label=\"{ option }\">\n <span class=\"dou-sheet-format__look\">\n <span\n class=\"dou-sheet-format__sample\"\n :style=\"{ backgroundImage: scaleSwatch(scaleColors(String(option.value))) }\"\n aria-hidden=\"true\"\n />\n {{ option.label }}\n </span>\n </template>\n </DouSelect>\n </DouFormItem>\n <DouFormItem v-else-if=\"kind === 'bar'\" :label=\"m.color\">\n <DouSelect v-model=\"bar\" :options=\"barOptions\">\n <template #option=\"{ option }\">\n <span class=\"dou-sheet-format__look\">\n <span\n class=\"dou-sheet-format__sample is-bar\"\n :style=\"{ backgroundImage: barSwatch(barOf(String(option.value))) }\"\n aria-hidden=\"true\"\n />\n {{ option.label }}\n </span>\n </template>\n <template #label=\"{ option }\">\n <span class=\"dou-sheet-format__look\">\n <span\n class=\"dou-sheet-format__sample is-bar\"\n :style=\"{ backgroundImage: barSwatch(barOf(String(option.value))) }\"\n aria-hidden=\"true\"\n />\n {{ option.label }}\n </span>\n </template>\n </DouSelect>\n </DouFormItem>\n <DouFormItem v-else :label=\"m.look\">\n <DouSelect v-model=\"look\" :options=\"lookOptions\">\n <template #option=\"{ option }\">\n <span class=\"dou-sheet-format__look\">\n <span\n class=\"dou-sheet-format__sample\"\n :class=\"lookSample(String(option.value)).cls\"\n :style=\"lookSample(String(option.value)).css\"\n aria-hidden=\"true\"\n >123</span\n >\n {{ option.label }}\n </span>\n </template>\n <template #label=\"{ option }\">\n <span class=\"dou-sheet-format__look\">\n <span\n class=\"dou-sheet-format__sample\"\n :class=\"lookSample(String(option.value)).cls\"\n :style=\"lookSample(String(option.value)).css\"\n aria-hidden=\"true\"\n >123</span\n >\n {{ option.label }}\n </span>\n </template>\n </DouSelect>\n </DouFormItem>\n </DouForm>\n <template #footer=\"{ close }\">\n <DouButton variant=\"outline\" @click=\"close\">{{ m.cancel }}</DouButton>\n <DouButton variant=\"solid\" @click=\"apply\">{{ m.apply }}</DouButton>\n </template>\n </DouDialog>\n</template>\n"],"mappings":""}
|