@mathscan/math-exercise-engine 1.0.10 → 1.0.12
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/dist/index.d.ts +1414 -3
- package/dist/index.js +726 -276
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +727 -277
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +294 -31
- package/package.json +1 -1
package/dist/style.css
CHANGED
|
@@ -100,6 +100,17 @@
|
|
|
100
100
|
|
|
101
101
|
/** Border width for inputs and buttons */
|
|
102
102
|
--exercise-border-width: 2px;
|
|
103
|
+
|
|
104
|
+
/* ========================================
|
|
105
|
+
NUMBER LINE — HIGHLIGHT COLORS
|
|
106
|
+
======================================== */
|
|
107
|
+
|
|
108
|
+
--nl-highlight-blue: rgba(0, 122, 204, 0.25);
|
|
109
|
+
--nl-highlight-red: rgba(239, 68, 68, 0.25);
|
|
110
|
+
--nl-highlight-green: rgba(34, 197, 94, 0.25);
|
|
111
|
+
--nl-highlight-orange: rgba(249, 115, 22, 0.25);
|
|
112
|
+
--nl-highlight-purple: rgba(112, 93, 192, 0.25);
|
|
113
|
+
--nl-highlight-yellow: rgba(255, 215, 0, 0.35);
|
|
103
114
|
}
|
|
104
115
|
|
|
105
116
|
/* Layout & Structure */
|
|
@@ -574,6 +585,8 @@ td.exercise-table-cell, th.exercise-table-cell {
|
|
|
574
585
|
margin-bottom: var(--exercise-margin-bottom);
|
|
575
586
|
user-select: none;
|
|
576
587
|
--nl-chip-zone-height: 3rem;
|
|
588
|
+
/* Visual gap between end ticks and arrowheads — pure CSS, no effect on calculations */
|
|
589
|
+
--nl-arrow-gap: 8px;
|
|
577
590
|
}
|
|
578
591
|
|
|
579
592
|
/* Chip zone: push the axis row down so chips overflowing upward
|
|
@@ -615,7 +628,7 @@ td.exercise-table-cell, th.exercise-table-cell {
|
|
|
615
628
|
|
|
616
629
|
/* In drag mode, chip zone is handled by .number-line--drag-mode padding-top on root */
|
|
617
630
|
|
|
618
|
-
/* Horizontal track line — spans [--nl-side-pad, 100% - --nl-side-pad] */
|
|
631
|
+
/* Horizontal track line — spans [--nl-side-pad, 100% - --nl-side-pad] by default */
|
|
619
632
|
|
|
620
633
|
.number-line-track {
|
|
621
634
|
position: absolute;
|
|
@@ -629,6 +642,25 @@ td.exercise-table-cell, th.exercise-table-cell {
|
|
|
629
642
|
transition: all 0.2s ease;
|
|
630
643
|
}
|
|
631
644
|
|
|
645
|
+
/* Extend the track toward the arrow only when the arrow is present —
|
|
646
|
+
keeps the track flush with the last tick when no arrow exists on that side */
|
|
647
|
+
|
|
648
|
+
.number-line-axis-row:has(.number-line-arrow-left) .number-line-track {
|
|
649
|
+
left: calc(var(--nl-side-pad, 7%) - var(--nl-arrow-gap, 8px));
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.number-line-axis-row:has(.number-line-arrow-right) .number-line-track {
|
|
653
|
+
right: calc(var(--nl-side-pad, 7%) - var(--nl-arrow-gap, 8px));
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/* Geometric line style — track extends to the full container edges
|
|
657
|
+
so the axis looks like an infinite line, not a closed segment */
|
|
658
|
+
|
|
659
|
+
.number-line--style-line .number-line-track {
|
|
660
|
+
left: 0;
|
|
661
|
+
right: 0;
|
|
662
|
+
}
|
|
663
|
+
|
|
632
664
|
/* ── Arrowheads (CSS border-triangle technique) ── */
|
|
633
665
|
|
|
634
666
|
.number-line-arrow {
|
|
@@ -638,26 +670,36 @@ td.exercise-table-cell, th.exercise-table-cell {
|
|
|
638
670
|
height: 0;
|
|
639
671
|
}
|
|
640
672
|
|
|
641
|
-
/* Right-pointing arrow — base sits at the right end of the track */
|
|
673
|
+
/* Right-pointing arrow — base sits at the right end of the track, offset outward by --nl-arrow-gap */
|
|
642
674
|
|
|
643
675
|
.number-line-arrow-right {
|
|
644
|
-
left: calc(100% - var(--nl-side-pad, 7%));
|
|
676
|
+
left: calc(100% - var(--nl-side-pad, 7%) + var(--nl-arrow-gap, 8px));
|
|
645
677
|
transform: translateY(-50%);
|
|
646
678
|
border-top: 5px solid transparent;
|
|
647
679
|
border-bottom: 5px solid transparent;
|
|
648
680
|
border-left: 8px solid var(--exercise-text, #333);
|
|
649
681
|
}
|
|
650
682
|
|
|
651
|
-
/* Left-pointing arrow — tip extends just past the left end of the track */
|
|
683
|
+
/* Left-pointing arrow — tip extends just past the left end of the track, offset outward by --nl-arrow-gap */
|
|
652
684
|
|
|
653
685
|
.number-line-arrow-left {
|
|
654
|
-
left: var(--nl-side-pad, 7%);
|
|
686
|
+
left: calc(var(--nl-side-pad, 7%) - var(--nl-arrow-gap, 8px));
|
|
655
687
|
transform: translate(-100%, -50%);
|
|
656
688
|
border-top: 5px solid transparent;
|
|
657
689
|
border-bottom: 5px solid transparent;
|
|
658
690
|
border-right: 8px solid var(--exercise-text, #333);
|
|
659
691
|
}
|
|
660
692
|
|
|
693
|
+
/* Geometric line style — arrows reach the full container edges */
|
|
694
|
+
|
|
695
|
+
.number-line--style-line .number-line-arrow-right {
|
|
696
|
+
left: calc(100%);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
.number-line--style-line .number-line-arrow-left {
|
|
700
|
+
left: 0;
|
|
701
|
+
}
|
|
702
|
+
|
|
661
703
|
/* ── Major ticks ── */
|
|
662
704
|
|
|
663
705
|
.number-line-major-tick {
|
|
@@ -667,6 +709,7 @@ td.exercise-table-cell, th.exercise-table-cell {
|
|
|
667
709
|
width: 2px;
|
|
668
710
|
height: 14px;
|
|
669
711
|
background-color: var(--exercise-text, #333);
|
|
712
|
+
transition: transform 0.1s ease, background-color 0.1s ease, width 0.1s ease;
|
|
670
713
|
}
|
|
671
714
|
|
|
672
715
|
/* ── Minor ticks ── */
|
|
@@ -678,6 +721,7 @@ td.exercise-table-cell, th.exercise-table-cell {
|
|
|
678
721
|
width: 1px;
|
|
679
722
|
height: 8px;
|
|
680
723
|
background-color: var(--exercise-text-light, #555);
|
|
724
|
+
transition: transform 0.1s ease, background-color 0.1s ease, width 0.1s ease;
|
|
681
725
|
}
|
|
682
726
|
|
|
683
727
|
/* ── Position marker dots ── */
|
|
@@ -686,12 +730,11 @@ td.exercise-table-cell, th.exercise-table-cell {
|
|
|
686
730
|
position: absolute;
|
|
687
731
|
top: 50%;
|
|
688
732
|
transform: translate(-50%, -50%);
|
|
689
|
-
width:
|
|
690
|
-
height:
|
|
733
|
+
width: 10px;
|
|
734
|
+
height: 10px;
|
|
691
735
|
border-radius: 50%;
|
|
692
|
-
background-color: var(--exercise-
|
|
693
|
-
|
|
694
|
-
box-shadow: 0 2px 4px rgba(0, 122, 204, 0.2);
|
|
736
|
+
background-color: var(--exercise-accent, #705DC0);
|
|
737
|
+
box-shadow: 0 1px 3px rgba(112, 93, 192, 0.25);
|
|
695
738
|
z-index: 2;
|
|
696
739
|
}
|
|
697
740
|
|
|
@@ -705,10 +748,20 @@ td.exercise-table-cell, th.exercise-table-cell {
|
|
|
705
748
|
*
|
|
706
749
|
* Structure:
|
|
707
750
|
* .number-line-placed-chip — absolute wrapper, positioned by left%
|
|
708
|
-
* .number-line-placed-chip-
|
|
751
|
+
* .number-line-placed-chip-inner — flex row holding btn + X button
|
|
752
|
+
* .number-line-placed-chip-btn — the visible chip button (draggable)
|
|
753
|
+
* .number-line-placed-chip-remove — ✕ badge (always visible)
|
|
709
754
|
* ::after pseudo — thin connector line down to the axis
|
|
710
755
|
*/
|
|
711
756
|
|
|
757
|
+
/* Drop landing animation — subtle scale for professional feel */
|
|
758
|
+
|
|
759
|
+
@keyframes nl-chip-land {
|
|
760
|
+
0% { transform: translateX(-50%) scale(0.75); opacity: 0.6; }
|
|
761
|
+
60% { transform: translateX(-50%) scale(1.06); opacity: 1; }
|
|
762
|
+
100% { transform: translateX(-50%) scale(1); opacity: 1; }
|
|
763
|
+
}
|
|
764
|
+
|
|
712
765
|
.number-line-placed-chip {
|
|
713
766
|
position: absolute;
|
|
714
767
|
bottom: calc(50% + 2px);
|
|
@@ -721,48 +774,134 @@ td.exercise-table-cell, th.exercise-table-cell {
|
|
|
721
774
|
pointer-events: none;
|
|
722
775
|
}
|
|
723
776
|
|
|
777
|
+
/* Play landing animation when class is toggled on by React */
|
|
778
|
+
|
|
779
|
+
.nl-chip-landing {
|
|
780
|
+
animation: nl-chip-land 0.28s ease-out forwards;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/* Hide original chip while DragOverlay clone follows cursor — no ghost double */
|
|
784
|
+
|
|
785
|
+
.nl-chip-dragging {
|
|
786
|
+
opacity: 0 !important;
|
|
787
|
+
pointer-events: none;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
.nl-chip-dragging::after {
|
|
791
|
+
display: none;
|
|
792
|
+
}
|
|
793
|
+
|
|
724
794
|
/* Thin connector between chip and axis */
|
|
725
795
|
|
|
726
796
|
.number-line-placed-chip::after {
|
|
727
797
|
content: '';
|
|
728
798
|
display: block;
|
|
729
|
-
width:
|
|
730
|
-
height:
|
|
731
|
-
background-color: var(--exercise-
|
|
732
|
-
|
|
799
|
+
width: 1.5px;
|
|
800
|
+
height: 10px;
|
|
801
|
+
background-color: var(--exercise-accent, #705DC0);
|
|
802
|
+
opacity: 0.5;
|
|
803
|
+
margin-top: 1px;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/* Item 3 — Inner flex row: position: relative so ✕ badge can be anchored inside */
|
|
807
|
+
|
|
808
|
+
.number-line-placed-chip-inner {
|
|
809
|
+
position: relative;
|
|
810
|
+
display: inline-flex;
|
|
811
|
+
align-items: center;
|
|
812
|
+
pointer-events: all;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/* ✕ badge: hidden by default, revealed on chip hover — clean, non-distracting */
|
|
816
|
+
|
|
817
|
+
.number-line-placed-chip-remove {
|
|
818
|
+
position: absolute;
|
|
819
|
+
top: -6px;
|
|
820
|
+
right: -6px;
|
|
821
|
+
display: flex;
|
|
822
|
+
align-items: center;
|
|
823
|
+
justify-content: center;
|
|
824
|
+
width: 16px;
|
|
825
|
+
height: 16px;
|
|
826
|
+
border-radius: 50%;
|
|
827
|
+
border: 1.5px solid white;
|
|
828
|
+
background-color: var(--exercise-text-light, #555);
|
|
829
|
+
color: white;
|
|
830
|
+
font-size: 0.5rem;
|
|
831
|
+
font-weight: bold;
|
|
832
|
+
line-height: 1;
|
|
833
|
+
cursor: pointer;
|
|
834
|
+
transition: opacity 0.15s ease, transform 0.12s ease, background-color 0.12s ease;
|
|
835
|
+
pointer-events: all;
|
|
836
|
+
z-index: 1;
|
|
837
|
+
opacity: 0;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/* Show ✕ on chip hover or when badge itself has focus */
|
|
841
|
+
|
|
842
|
+
.number-line-placed-chip-inner:hover .number-line-placed-chip-remove,
|
|
843
|
+
.number-line-placed-chip-remove:focus-visible {
|
|
844
|
+
opacity: 1;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
.number-line-placed-chip-remove:hover {
|
|
848
|
+
opacity: 1;
|
|
849
|
+
transform: scale(1.15);
|
|
850
|
+
background-color: var(--exercise-danger, #ef4444);
|
|
733
851
|
}
|
|
734
852
|
|
|
735
853
|
.number-line-placed-chip-btn {
|
|
736
854
|
pointer-events: all;
|
|
737
|
-
background-color: var(--exercise-
|
|
855
|
+
background-color: var(--exercise-accent, #705DC0);
|
|
738
856
|
color: white;
|
|
739
|
-
|
|
740
|
-
border:
|
|
741
|
-
|
|
742
|
-
padding: var(--exercise-input-padding, 0.375rem 0.625rem);
|
|
857
|
+
border: 2px solid rgba(255, 255, 255, 0.2);
|
|
858
|
+
border-radius: 8px;
|
|
859
|
+
padding: 6px 14px;
|
|
743
860
|
font-size: var(--exercise-font-size, 0.875rem);
|
|
744
861
|
font-weight: var(--exercise-font-weight-bold, 600);
|
|
745
862
|
cursor: pointer;
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
transition: all 0.2s ease;
|
|
863
|
+
box-shadow: 0 2px 8px rgba(112, 93, 192, 0.25);
|
|
864
|
+
transition: all 0.15s ease;
|
|
749
865
|
white-space: nowrap;
|
|
750
866
|
}
|
|
751
867
|
|
|
752
868
|
.number-line-placed-chip-btn:hover {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
transform: scale(0.98);
|
|
869
|
+
filter: brightness(1.08);
|
|
870
|
+
box-shadow: 0 3px 10px rgba(112, 93, 192, 0.35);
|
|
756
871
|
}
|
|
757
872
|
|
|
758
|
-
/*
|
|
873
|
+
/* Nearest snap tick highlights during drag — scaleY keeps layout intact */
|
|
874
|
+
|
|
875
|
+
.number-line-major-tick.nl-tick-hover {
|
|
876
|
+
transform: translate(-50%, -50%) scaleY(1.5);
|
|
877
|
+
width: 2.5px;
|
|
878
|
+
background-color: var(--exercise-accent, #705DC0);
|
|
879
|
+
}
|
|
759
880
|
|
|
760
|
-
.
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
881
|
+
.number-line-minor-tick.nl-tick-hover {
|
|
882
|
+
transform: translate(-50%, -50%) scaleY(1.8);
|
|
883
|
+
width: 1.5px;
|
|
884
|
+
background-color: var(--exercise-accent, #705DC0);
|
|
764
885
|
}
|
|
765
886
|
|
|
887
|
+
/* Snap preview — static subtle dot at landing position */
|
|
888
|
+
|
|
889
|
+
.nl-snap-preview {
|
|
890
|
+
position: absolute;
|
|
891
|
+
top: 50%;
|
|
892
|
+
transform: translate(-50%, -50%);
|
|
893
|
+
width: 12px;
|
|
894
|
+
height: 12px;
|
|
895
|
+
border-radius: 50%;
|
|
896
|
+
background-color: var(--exercise-accent, #705DC0);
|
|
897
|
+
opacity: 0.3;
|
|
898
|
+
pointer-events: none;
|
|
899
|
+
z-index: 2;
|
|
900
|
+
transition: left 0.06s ease;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/* Axis glow on drag-over intentionally disabled — axis stays neutral */
|
|
904
|
+
|
|
766
905
|
/* ========================================
|
|
767
906
|
ROW 3 — NUMERIC TICK LABELS
|
|
768
907
|
======================================== */
|
|
@@ -783,6 +922,130 @@ td.exercise-table-cell, th.exercise-table-cell {
|
|
|
783
922
|
white-space: nowrap;
|
|
784
923
|
}
|
|
785
924
|
|
|
925
|
+
/* ========================================
|
|
926
|
+
HIGHLIGHT BANDS
|
|
927
|
+
======================================== */
|
|
928
|
+
|
|
929
|
+
/* A coloured band spanning [from%, to%] on the axis row */
|
|
930
|
+
|
|
931
|
+
.number-line-highlight-band {
|
|
932
|
+
position: absolute;
|
|
933
|
+
top: 0;
|
|
934
|
+
height: 100%;
|
|
935
|
+
background-color: var(--nl-highlight-color, var(--nl-highlight-blue, rgba(0, 122, 204, 0.25)));
|
|
936
|
+
border-radius: 2px;
|
|
937
|
+
z-index: 0;
|
|
938
|
+
pointer-events: none;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/* Highlight label row — sits below tick labels, never overlaps inputs */
|
|
942
|
+
|
|
943
|
+
.number-line-highlight-labels-row {
|
|
944
|
+
position: relative;
|
|
945
|
+
width: 100%;
|
|
946
|
+
height: 1.2rem;
|
|
947
|
+
margin-top: 0.1rem;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
.number-line-highlight-label {
|
|
951
|
+
position: absolute;
|
|
952
|
+
transform: translateX(-50%);
|
|
953
|
+
font-size: calc(var(--exercise-font-size) * 0.75);
|
|
954
|
+
font-weight: var(--exercise-font-weight-bold, 600);
|
|
955
|
+
color: var(--exercise-text-light, #555);
|
|
956
|
+
white-space: nowrap;
|
|
957
|
+
pointer-events: none;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/* ========================================
|
|
961
|
+
COUNTER UI
|
|
962
|
+
======================================== */
|
|
963
|
+
|
|
964
|
+
.number-line-counter {
|
|
965
|
+
display: flex;
|
|
966
|
+
flex-direction: column;
|
|
967
|
+
align-items: center;
|
|
968
|
+
gap: 0.25rem;
|
|
969
|
+
margin-top: 0.5rem;
|
|
970
|
+
padding: 0.4rem 0.75rem;
|
|
971
|
+
width: fit-content;
|
|
972
|
+
margin-left: auto;
|
|
973
|
+
margin-right: auto;
|
|
974
|
+
background: var(--exercise-bg, white);
|
|
975
|
+
border: 1px solid var(--exercise-border, #ddd);
|
|
976
|
+
border-radius: 8px;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/* Inner row: − value + buttons as a cohesive stepper */
|
|
980
|
+
|
|
981
|
+
.number-line-counter-controls {
|
|
982
|
+
display: flex;
|
|
983
|
+
align-items: center;
|
|
984
|
+
justify-content: center;
|
|
985
|
+
gap: 0.5rem;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
.number-line-counter-label {
|
|
989
|
+
font-size: calc(var(--exercise-font-size) * 0.85);
|
|
990
|
+
color: var(--exercise-text-light, #555);
|
|
991
|
+
text-align: center;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
.number-line-counter-value {
|
|
995
|
+
font-size: calc(var(--exercise-font-size) * 1.15);
|
|
996
|
+
font-weight: 600;
|
|
997
|
+
font-variant-numeric: tabular-nums;
|
|
998
|
+
color: var(--exercise-text, #333);
|
|
999
|
+
min-width: 2ch;
|
|
1000
|
+
text-align: center;
|
|
1001
|
+
line-height: 1;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
.number-line-counter-btn {
|
|
1005
|
+
display: inline-flex;
|
|
1006
|
+
align-items: center;
|
|
1007
|
+
justify-content: center;
|
|
1008
|
+
min-width: 36px;
|
|
1009
|
+
min-height: 36px;
|
|
1010
|
+
width: 36px;
|
|
1011
|
+
height: 36px;
|
|
1012
|
+
border: 1.5px solid var(--exercise-accent, #705DC0);
|
|
1013
|
+
border-radius: 50%;
|
|
1014
|
+
background: transparent;
|
|
1015
|
+
color: var(--exercise-accent, #705DC0);
|
|
1016
|
+
font-size: 1.1rem;
|
|
1017
|
+
font-weight: 500;
|
|
1018
|
+
line-height: 1;
|
|
1019
|
+
cursor: pointer;
|
|
1020
|
+
transition: all 0.12s ease;
|
|
1021
|
+
-webkit-tap-highlight-color: transparent;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
.number-line-counter-btn:hover:not(:disabled) {
|
|
1025
|
+
background: var(--exercise-accent, #705DC0);
|
|
1026
|
+
color: white;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
.number-line-counter-btn:active:not(:disabled) {
|
|
1030
|
+
transform: scale(0.93);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
.number-line-counter-btn:disabled {
|
|
1034
|
+
opacity: 0.35;
|
|
1035
|
+
cursor: not-allowed;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/* ========================================
|
|
1039
|
+
ACCESSIBILITY — FOCUS-VISIBLE RINGS (item 6)
|
|
1040
|
+
======================================== */
|
|
1041
|
+
|
|
1042
|
+
.number-line-placed-chip-btn:focus-visible,
|
|
1043
|
+
.number-line-placed-chip-remove:focus-visible,
|
|
1044
|
+
.number-line-counter-btn:focus-visible {
|
|
1045
|
+
outline: 2px solid var(--exercise-accent, #705DC0);
|
|
1046
|
+
outline-offset: 2px;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
786
1049
|
/**
|
|
787
1050
|
* Button & Action Styles
|
|
788
1051
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mathscan/math-exercise-engine",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"description": "A DSL parser and validator for math exercises - transforms text-based exercise definitions into structured AST and JSON",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|