@jjlmoya/utils-civic 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/category/index.ts +3 -1
- package/src/entries.ts +4 -0
- package/src/index.ts +1 -0
- package/src/tests/locale_completeness.test.ts +1 -1
- package/src/tests/tool_validation.test.ts +1 -1
- package/src/tool/coalition-majority-calculator/bibliography.astro +6 -0
- package/src/tool/coalition-majority-calculator/bibliography.ts +12 -0
- package/src/tool/coalition-majority-calculator/coalition-majority-calculator.css +467 -0
- package/src/tool/coalition-majority-calculator/component.astro +62 -0
- package/src/tool/coalition-majority-calculator/contract.test.ts +14 -0
- package/src/tool/coalition-majority-calculator/controller.ts +89 -0
- package/src/tool/coalition-majority-calculator/dom-views.ts +145 -0
- package/src/tool/coalition-majority-calculator/entry.ts +28 -0
- package/src/tool/coalition-majority-calculator/evaluator.ts +13 -0
- package/src/tool/coalition-majority-calculator/i18n/de.ts +51 -0
- package/src/tool/coalition-majority-calculator/i18n/en.ts +74 -0
- package/src/tool/coalition-majority-calculator/i18n/es.ts +51 -0
- package/src/tool/coalition-majority-calculator/i18n/fr.ts +12 -0
- package/src/tool/coalition-majority-calculator/i18n/id.ts +45 -0
- package/src/tool/coalition-majority-calculator/i18n/it.ts +12 -0
- package/src/tool/coalition-majority-calculator/i18n/ja.ts +21 -0
- package/src/tool/coalition-majority-calculator/i18n/ko.ts +21 -0
- package/src/tool/coalition-majority-calculator/i18n/nl.ts +12 -0
- package/src/tool/coalition-majority-calculator/i18n/pl.ts +12 -0
- package/src/tool/coalition-majority-calculator/i18n/pt.ts +12 -0
- package/src/tool/coalition-majority-calculator/i18n/ru.ts +12 -0
- package/src/tool/coalition-majority-calculator/i18n/sv.ts +12 -0
- package/src/tool/coalition-majority-calculator/i18n/tr.ts +12 -0
- package/src/tool/coalition-majority-calculator/i18n/zh.ts +12 -0
- package/src/tool/coalition-majority-calculator/index.ts +11 -0
- package/src/tool/coalition-majority-calculator/logic.test.ts +60 -0
- package/src/tool/coalition-majority-calculator/logic.ts +146 -0
- package/src/tool/coalition-majority-calculator/seo.astro +12 -0
- package/src/tool/coalition-majority-calculator/storage.ts +32 -0
- package/src/tool/coalition-majority-calculator/ui.ts +80 -0
- package/src/tool/referendum-threshold-calculator/bibliography.astro +6 -0
- package/src/tool/referendum-threshold-calculator/bibliography.ts +6 -0
- package/src/tool/referendum-threshold-calculator/component.astro +117 -0
- package/src/tool/referendum-threshold-calculator/controller.ts +141 -0
- package/src/tool/referendum-threshold-calculator/dom-views.ts +71 -0
- package/src/tool/referendum-threshold-calculator/entry.ts +27 -0
- package/src/tool/referendum-threshold-calculator/evaluator.ts +19 -0
- package/src/tool/referendum-threshold-calculator/i18n/de.ts +56 -0
- package/src/tool/referendum-threshold-calculator/i18n/en.ts +79 -0
- package/src/tool/referendum-threshold-calculator/i18n/es.ts +56 -0
- package/src/tool/referendum-threshold-calculator/i18n/fr.ts +56 -0
- package/src/tool/referendum-threshold-calculator/i18n/id.ts +56 -0
- package/src/tool/referendum-threshold-calculator/i18n/it.ts +56 -0
- package/src/tool/referendum-threshold-calculator/i18n/ja.ts +56 -0
- package/src/tool/referendum-threshold-calculator/i18n/ko.ts +56 -0
- package/src/tool/referendum-threshold-calculator/i18n/nl.ts +56 -0
- package/src/tool/referendum-threshold-calculator/i18n/pl.ts +56 -0
- package/src/tool/referendum-threshold-calculator/i18n/pt.ts +56 -0
- package/src/tool/referendum-threshold-calculator/i18n/ru.ts +56 -0
- package/src/tool/referendum-threshold-calculator/i18n/sv.ts +56 -0
- package/src/tool/referendum-threshold-calculator/i18n/tr.ts +56 -0
- package/src/tool/referendum-threshold-calculator/i18n/zh.ts +56 -0
- package/src/tool/referendum-threshold-calculator/index.ts +11 -0
- package/src/tool/referendum-threshold-calculator/logic.test.ts +69 -0
- package/src/tool/referendum-threshold-calculator/logic.ts +120 -0
- package/src/tool/referendum-threshold-calculator/referendum-threshold-calculator.css +640 -0
- package/src/tool/referendum-threshold-calculator/seo.astro +14 -0
- package/src/tool/referendum-threshold-calculator/sharing.test.ts +23 -0
- package/src/tool/referendum-threshold-calculator/sharing.ts +42 -0
- package/src/tool/referendum-threshold-calculator/storage.ts +39 -0
- package/src/tool/referendum-threshold-calculator/ui.ts +126 -0
- package/src/tools.ts +4 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ReferendumInput } from './logic';
|
|
2
|
+
|
|
3
|
+
const STORAGE_KEY = 'jjlmoya-civic-referendum-threshold-v1';
|
|
4
|
+
|
|
5
|
+
function hasStorage(): boolean {
|
|
6
|
+
return typeof window !== 'undefined' && typeof window.localStorage !== 'undefined';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isSavedInput(value: unknown): value is ReferendumInput {
|
|
10
|
+
if (!value || typeof value !== 'object') return false;
|
|
11
|
+
const candidate = value as Record<string, unknown>;
|
|
12
|
+
return Boolean(candidate.counts && candidate.rules);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function loadSavedInput(): ReferendumInput | null {
|
|
16
|
+
if (!hasStorage()) return null;
|
|
17
|
+
try {
|
|
18
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
19
|
+
if (!raw) return null;
|
|
20
|
+
const parsed: unknown = JSON.parse(raw);
|
|
21
|
+
return isSavedInput(parsed) ? parsed : null;
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function saveInput(input: ReferendumInput): void {
|
|
28
|
+
if (!hasStorage()) return;
|
|
29
|
+
try {
|
|
30
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(input));
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function clearSavedInput(): void {
|
|
35
|
+
if (!hasStorage()) return;
|
|
36
|
+
try {
|
|
37
|
+
window.localStorage.removeItem(STORAGE_KEY);
|
|
38
|
+
} catch {}
|
|
39
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
export interface ReferendumThresholdUI {
|
|
2
|
+
[key: string]: string;
|
|
3
|
+
countsHeading: string;
|
|
4
|
+
eligibleLabel: string;
|
|
5
|
+
eligibleHelp: string;
|
|
6
|
+
yesLabel: string;
|
|
7
|
+
noLabel: string;
|
|
8
|
+
blankLabel: string;
|
|
9
|
+
invalidLabel: string;
|
|
10
|
+
participationHeading: string;
|
|
11
|
+
participationHelp: string;
|
|
12
|
+
approvalHeading: string;
|
|
13
|
+
approvalHelp: string;
|
|
14
|
+
thresholdModeLabel: string;
|
|
15
|
+
comparisonLabel: string;
|
|
16
|
+
atLeastOption: string;
|
|
17
|
+
moreThanOption: string;
|
|
18
|
+
percentageOption: string;
|
|
19
|
+
absoluteOption: string;
|
|
20
|
+
thresholdValueLabel: string;
|
|
21
|
+
denominatorLabel: string;
|
|
22
|
+
allBallotsOption: string;
|
|
23
|
+
validVotesOption: string;
|
|
24
|
+
eligibleVotersOption: string;
|
|
25
|
+
presetsLabel: string;
|
|
26
|
+
simpleMajorityPreset: string;
|
|
27
|
+
electorateMajorityPreset: string;
|
|
28
|
+
qualified55Preset: string;
|
|
29
|
+
qualified60Preset: string;
|
|
30
|
+
loadExample: string;
|
|
31
|
+
shareAction: string;
|
|
32
|
+
shareSuccess: string;
|
|
33
|
+
shareFailure: string;
|
|
34
|
+
resultHeading: string;
|
|
35
|
+
emptyResult: string;
|
|
36
|
+
passed: string;
|
|
37
|
+
participationFailed: string;
|
|
38
|
+
approvalFailed: string;
|
|
39
|
+
bothFailed: string;
|
|
40
|
+
invalidResult: string;
|
|
41
|
+
participationMetric: string;
|
|
42
|
+
approvalMetric: string;
|
|
43
|
+
observedLabel: string;
|
|
44
|
+
thresholdLabel: string;
|
|
45
|
+
decisionPathLabel: string;
|
|
46
|
+
methodHeading: string;
|
|
47
|
+
methodText: string;
|
|
48
|
+
limitsHeading: string;
|
|
49
|
+
limitsText: string;
|
|
50
|
+
warningsHeading: string;
|
|
51
|
+
warningsText: string;
|
|
52
|
+
invalidInput: string;
|
|
53
|
+
ballotsExceedEligible: string;
|
|
54
|
+
noValidVotes: string;
|
|
55
|
+
noBallots: string;
|
|
56
|
+
peopleSuffix: string;
|
|
57
|
+
yesShareLabel: string;
|
|
58
|
+
allBallotsLabel: string;
|
|
59
|
+
validVotesLabel: string;
|
|
60
|
+
eligibleVotersLabel: string;
|
|
61
|
+
whatNextLabel: string;
|
|
62
|
+
whatNextText: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const ui: ReferendumThresholdUI = {
|
|
66
|
+
countsHeading: 'Enter the declared vote counts',
|
|
67
|
+
eligibleLabel: 'Eligible or registered voters',
|
|
68
|
+
eligibleHelp: 'Use the electorate denominator named by the rule you are checking.',
|
|
69
|
+
yesLabel: 'Yes votes',
|
|
70
|
+
noLabel: 'No votes',
|
|
71
|
+
blankLabel: 'Blank votes',
|
|
72
|
+
invalidLabel: 'Invalid votes',
|
|
73
|
+
participationHeading: 'Participation quorum',
|
|
74
|
+
participationHelp: 'Choose what counts as participation and the threshold it must reach.',
|
|
75
|
+
approvalHeading: 'Approval quorum',
|
|
76
|
+
approvalHelp: 'Choose the denominator for the Yes threshold. Every rule stays visible in the result.',
|
|
77
|
+
thresholdModeLabel: 'Threshold type',
|
|
78
|
+
comparisonLabel: 'Comparison',
|
|
79
|
+
atLeastOption: 'At least',
|
|
80
|
+
moreThanOption: 'More than',
|
|
81
|
+
percentageOption: 'Percentage',
|
|
82
|
+
absoluteOption: 'Absolute number',
|
|
83
|
+
thresholdValueLabel: 'Threshold value',
|
|
84
|
+
denominatorLabel: 'Count used',
|
|
85
|
+
allBallotsOption: 'All ballots',
|
|
86
|
+
validVotesOption: 'Valid votes only',
|
|
87
|
+
eligibleVotersOption: 'Eligible voters',
|
|
88
|
+
presetsLabel: 'Approval presets',
|
|
89
|
+
simpleMajorityPreset: 'Simple majority',
|
|
90
|
+
electorateMajorityPreset: 'Electorate majority',
|
|
91
|
+
qualified55Preset: 'Qualified 55%',
|
|
92
|
+
qualified60Preset: 'Qualified 60%',
|
|
93
|
+
loadExample: 'Load example',
|
|
94
|
+
shareAction: 'Copy permalink',
|
|
95
|
+
shareSuccess: 'Permalink copied. It contains the counts and both declared rules.',
|
|
96
|
+
shareFailure: 'The permalink is in the address bar. Copy it when you are ready to share.',
|
|
97
|
+
resultHeading: 'Declared rule decision',
|
|
98
|
+
emptyResult: 'Enter counts and thresholds to see the two quorum tests.',
|
|
99
|
+
passed: 'Both thresholds pass',
|
|
100
|
+
participationFailed: 'Participation quorum fails',
|
|
101
|
+
approvalFailed: 'Approval quorum fails',
|
|
102
|
+
bothFailed: 'Both thresholds fail',
|
|
103
|
+
invalidResult: 'Check the declared counts',
|
|
104
|
+
participationMetric: 'Participation test',
|
|
105
|
+
approvalMetric: 'Approval test',
|
|
106
|
+
observedLabel: 'Observed',
|
|
107
|
+
thresholdLabel: 'Required',
|
|
108
|
+
decisionPathLabel: 'Decision path',
|
|
109
|
+
methodHeading: 'Method applied',
|
|
110
|
+
methodText: 'Participation is the selected ballot count divided by eligible voters. Approval is Yes divided by the selected approval denominator. The comparison control decides whether an exact percentage passes or the observed value must be greater. Absolute thresholds compare counts directly.',
|
|
111
|
+
limitsHeading: 'What this tool does not do',
|
|
112
|
+
limitsText: 'It does not identify the competent authority, determine a statutory deadline, guarantee disclosure, reproduce an official count, file a referendum, or provide legal advice. The rule is only as valid as the assumptions you enter.',
|
|
113
|
+
warningsHeading: 'Edge cases and data warnings',
|
|
114
|
+
warningsText: 'Broad or mixed ballot definitions can change the result. Check whether blanks and invalid ballots belong in the participation count, whether the electorate is current, and whether the jurisdiction defines approval against valid votes or all ballots.',
|
|
115
|
+
invalidInput: 'Use whole, nonnegative counts. Eligible voters must be greater than zero and the ballot total cannot exceed the electorate.',
|
|
116
|
+
ballotsExceedEligible: 'The declared ballots exceed the eligible electorate.',
|
|
117
|
+
noValidVotes: 'There are no Yes or No votes, so the approval percentage has no valid vote denominator.',
|
|
118
|
+
noBallots: 'No ballots were declared, so participation and approval cannot pass unless the chosen rules have a zero threshold.',
|
|
119
|
+
peopleSuffix: 'people',
|
|
120
|
+
yesShareLabel: 'Yes share',
|
|
121
|
+
allBallotsLabel: 'all ballots',
|
|
122
|
+
validVotesLabel: 'valid votes',
|
|
123
|
+
eligibleVotersLabel: 'eligible voters',
|
|
124
|
+
whatNextLabel: 'Before you rely on the result',
|
|
125
|
+
whatNextText: 'Copy the exact rule wording and ballot definitions from the applicable official source, then compare them with the configuration shown here.',
|
|
126
|
+
};
|
package/src/tools.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
export { ALL_ENTRIES } from './entries';
|
|
2
2
|
import type { ToolDefinition } from './types';
|
|
3
|
+
import { COALITION_MAJORITY_CALCULATOR_TOOL } from './tool/coalition-majority-calculator';
|
|
3
4
|
import { ELECTION_SEAT_APPORTIONMENT_CALCULATOR_TOOL } from './tool/election-seat-apportionment-calculator';
|
|
5
|
+
import { REFERENDUM_THRESHOLD_CALCULATOR_TOOL } from './tool/referendum-threshold-calculator';
|
|
4
6
|
import { VOTE_SEAT_DISPROPORTIONALITY_ANALYZER_TOOL } from './tool/vote-seat-disproportionality-analyzer';
|
|
5
7
|
|
|
6
8
|
export const ALL_TOOLS: ToolDefinition[] = [
|
|
9
|
+
COALITION_MAJORITY_CALCULATOR_TOOL,
|
|
7
10
|
ELECTION_SEAT_APPORTIONMENT_CALCULATOR_TOOL,
|
|
11
|
+
REFERENDUM_THRESHOLD_CALCULATOR_TOOL,
|
|
8
12
|
VOTE_SEAT_DISPROPORTIONALITY_ANALYZER_TOOL,
|
|
9
13
|
];
|